Repository: apache/casbin
Branch: master
Commit: 098508551fe3
Files: 225
Total size: 1018.9 KB
Directory structure:
gitextract_1o8_iigh/
├── .asf.yaml
├── .github/
│ ├── scripts/
│ │ ├── benchmark_formatter.py
│ │ ├── download_artifact.js
│ │ └── post_comment.js
│ ├── semantic.yml
│ └── workflows/
│ ├── comment.yml
│ ├── default.yml
│ ├── golangci-lint.yml
│ └── performance-pr.yml
├── .gitignore
├── .golangci.yml
├── .releaserc.json
├── CONTRIBUTING.md
├── DISCLAIMER
├── LICENSE
├── Makefile
├── README.md
├── abac_test.go
├── ai_api.go
├── ai_api_test.go
├── biba_test.go
├── blp_test.go
├── config/
│ ├── config.go
│ ├── config_test.go
│ └── testdata/
│ └── testini.ini
├── constant/
│ └── constants.go
├── constraint_test.go
├── detector/
│ ├── default_detector.go
│ ├── default_detector_test.go
│ └── detector.go
├── effector/
│ ├── default_effector.go
│ └── effector.go
├── enforcer.go
├── enforcer_backslash_test.go
├── enforcer_cached.go
├── enforcer_cached_b_test.go
├── enforcer_cached_gfunction_test.go
├── enforcer_cached_synced.go
├── enforcer_cached_synced_test.go
├── enforcer_cached_test.go
├── enforcer_context.go
├── enforcer_context_interface.go
├── enforcer_context_test.go
├── enforcer_distributed.go
├── enforcer_interface.go
├── enforcer_json_test.go
├── enforcer_synced.go
├── enforcer_synced_test.go
├── enforcer_test.go
├── enforcer_transactional.go
├── error_test.go
├── errors/
│ ├── constraint_errors.go
│ └── rbac_errors.go
├── examples/
│ ├── abac_model.conf
│ ├── abac_not_using_policy_model.conf
│ ├── abac_rule_effect_policy.csv
│ ├── abac_rule_model.conf
│ ├── abac_rule_policy.csv
│ ├── basic_inverse_policy.csv
│ ├── basic_model.conf
│ ├── basic_model_without_spaces.conf
│ ├── basic_policy.csv
│ ├── basic_with_root_model.conf
│ ├── basic_without_resources_model.conf
│ ├── basic_without_resources_policy.csv
│ ├── basic_without_users_model.conf
│ ├── basic_without_users_policy.csv
│ ├── biba_model.conf
│ ├── blp_model.conf
│ ├── comment_model.conf
│ ├── error/
│ │ ├── error_model.conf
│ │ └── error_policy.csv
│ ├── eval_operator_model.conf
│ ├── eval_operator_policy.csv
│ ├── glob_model.conf
│ ├── glob_policy.csv
│ ├── ipmatch_model.conf
│ ├── ipmatch_policy.csv
│ ├── keyget2_model.conf
│ ├── keyget_model.conf
│ ├── keymatch2_model.conf
│ ├── keymatch2_policy.csv
│ ├── keymatch_custom_model.conf
│ ├── keymatch_model.conf
│ ├── keymatch_policy.csv
│ ├── keymatch_with_rbac_in_domain.conf
│ ├── keymatch_with_rbac_in_domain.csv
│ ├── lbac_model.conf
│ ├── multiple_policy_definitions_model.conf
│ ├── multiple_policy_definitions_policy.csv
│ ├── object_conditions_model.conf
│ ├── object_conditions_policy.csv
│ ├── orbac_model.conf
│ ├── orbac_policy.csv
│ ├── pbac_model.conf
│ ├── pbac_policy.csv
│ ├── performance/
│ │ ├── rbac_with_pattern_large_scale_model.conf
│ │ └── rbac_with_pattern_large_scale_policy.csv
│ ├── priority_indeterminate_policy.csv
│ ├── priority_model.conf
│ ├── priority_model_enforce_context.conf
│ ├── priority_model_explicit.conf
│ ├── priority_model_explicit_customized.conf
│ ├── priority_policy.csv
│ ├── priority_policy_enforce_context.csv
│ ├── priority_policy_explicit.csv
│ ├── priority_policy_explicit_customized.csv
│ ├── rbac_model.conf
│ ├── rbac_model_in_multi_line.conf
│ ├── rbac_model_matcher_using_in_op.conf
│ ├── rbac_model_matcher_using_in_op_bracket.conf
│ ├── rbac_policy.csv
│ ├── rbac_with_all_pattern_model.conf
│ ├── rbac_with_all_pattern_policy.csv
│ ├── rbac_with_constraints_model.conf
│ ├── rbac_with_cycle_policy.csv
│ ├── rbac_with_deny_model.conf
│ ├── rbac_with_deny_policy.csv
│ ├── rbac_with_different_types_of_roles_model.conf
│ ├── rbac_with_different_types_of_roles_policy.csv
│ ├── rbac_with_domain_pattern_model.conf
│ ├── rbac_with_domain_pattern_policy.csv
│ ├── rbac_with_domain_temporal_roles_model.conf
│ ├── rbac_with_domain_temporal_roles_policy.csv
│ ├── rbac_with_domains_conditional_model.conf
│ ├── rbac_with_domains_conditional_policy.csv
│ ├── rbac_with_domains_model.conf
│ ├── rbac_with_domains_policy.csv
│ ├── rbac_with_domains_policy2.csv
│ ├── rbac_with_hierarchy_policy.csv
│ ├── rbac_with_hierarchy_with_domains_policy.csv
│ ├── rbac_with_multiple_policy_model.conf
│ ├── rbac_with_multiple_policy_policy.csv
│ ├── rbac_with_not_deny_model.conf
│ ├── rbac_with_pattern_model.conf
│ ├── rbac_with_pattern_policy.csv
│ ├── rbac_with_resource_roles_model.conf
│ ├── rbac_with_resource_roles_policy.csv
│ ├── rbac_with_temporal_roles_model.conf
│ ├── rbac_with_temporal_roles_policy.csv
│ ├── rebac_model.conf
│ ├── rebac_policy.csv
│ ├── subject_priority_model.conf
│ ├── subject_priority_model_with_domain.conf
│ ├── subject_priority_policy.csv
│ ├── subject_priority_policy_with_domain.csv
│ └── syntax_matcher_model.conf
├── filter_test.go
├── frontend.go
├── frontend_old.go
├── frontend_old_test.go
├── frontend_test.go
├── go.mod
├── go.sum
├── internal_api.go
├── lbac_test.go
├── log/
│ ├── default_logger.go
│ ├── logger.go
│ └── types.go
├── logger_test.go
├── management_api.go
├── management_api_b_test.go
├── management_api_test.go
├── model/
│ ├── assertion.go
│ ├── constraint.go
│ ├── function.go
│ ├── model.go
│ ├── model_test.go
│ └── policy.go
├── model_b_test.go
├── model_test.go
├── orbac_test.go
├── pbac_test.go
├── persist/
│ ├── adapter.go
│ ├── adapter_context.go
│ ├── adapter_filtered.go
│ ├── adapter_filtered_context.go
│ ├── batch_adapter.go
│ ├── batch_adapter_context.go
│ ├── cache/
│ │ ├── cache.go
│ │ ├── cache_sync.go
│ │ └── default-cache.go
│ ├── dispatcher.go
│ ├── file-adapter/
│ │ ├── adapter.go
│ │ ├── adapter_context.go
│ │ ├── adapter_filtered.go
│ │ ├── adapter_filtered_context.go
│ │ └── adapter_mock.go
│ ├── persist_test.go
│ ├── string-adapter/
│ │ ├── adapter.go
│ │ ├── adapter_context.go
│ │ └── adapter_test.go
│ ├── transaction.go
│ ├── update_adapter.go
│ ├── update_adapter_context.go
│ ├── watcher.go
│ ├── watcher_ex.go
│ └── watcher_update.go
├── rbac/
│ ├── context_role_manager.go
│ ├── default-role-manager/
│ │ ├── role_manager.go
│ │ └── role_manager_test.go
│ └── role_manager.go
├── rbac_api.go
├── rbac_api_context.go
├── rbac_api_synced.go
├── rbac_api_test.go
├── rbac_api_with_domains.go
├── rbac_api_with_domains_context.go
├── rbac_api_with_domains_synced.go
├── rbac_api_with_domains_test.go
├── role_manager_b_test.go
├── syntax_test.go
├── transaction.go
├── transaction_buffer.go
├── transaction_commit.go
├── transaction_conflict.go
├── transaction_test.go
├── util/
│ ├── builtin_operators.go
│ ├── builtin_operators_test.go
│ ├── util.go
│ └── util_test.go
├── util_log.go
├── watcher_ex_test.go
├── watcher_test.go
└── watcher_update_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .asf.yaml
================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# For more information, see https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features.
github:
description: >-
Apache Casbin: an authorization library that supports access control models like ACL, RBAC, ABAC.
homepage: https://casbin.apache.org/
dependabot_alerts: true
dependabot_updates: false
notifications:
commits: commits@casbin.apache.org
================================================
FILE: .github/scripts/benchmark_formatter.py
================================================
import pathlib, re, sys
try:
p = pathlib.Path("comparison.md")
if not p.exists():
print("comparison.md not found, skipping post-processing.")
sys.exit(0)
lines = p.read_text(encoding="utf-8").splitlines()
processed_lines = []
in_code = False
def strip_worker_suffix(text: str) -> str:
return re.sub(r'(\S+?)-\d+(\s|$)', r'\1\2', text)
def get_icon(diff_val: float) -> str:
if diff_val > 10:
return "🐌"
if diff_val < -10:
return "🚀"
return "➡️"
def clean_superscripts(text: str) -> str:
return re.sub(r'[¹²³⁴⁵⁶⁷⁸⁹⁰]', '', text)
def parse_val(token: str):
if '%' in token or '=' in token:
return None
token = clean_superscripts(token)
token = token.split('±')[0].strip()
token = token.split('(')[0].strip()
if not token:
return None
m = re.match(r'^([-+]?\d*\.?\d+)([a-zA-Zµ]+)?$', token)
if not m:
return None
try:
val = float(m.group(1))
except ValueError:
return None
suffix = (m.group(2) or "").replace("µ", "u")
multipliers = {
"n": 1e-9,
"ns": 1e-9,
"u": 1e-6,
"us": 1e-6,
"m": 1e-3,
"ms": 1e-3,
"s": 1.0,
"k": 1e3,
"K": 1e3,
"M": 1e6,
"G": 1e9,
"Ki": 1024.0,
"Mi": 1024.0**2,
"Gi": 1024.0**3,
"Ti": 1024.0**4,
"B": 1.0,
"B/op": 1.0,
"C": 1.0, # tolerate degree/unit markers that don't affect ratio
}
return val * multipliers.get(suffix, 1.0)
def extract_two_numbers(tokens):
found = []
for t in tokens[1:]: # skip name
if t in {"±", "∞", "~", "│", "│"}:
continue
if '%' in t or '=' in t:
continue
val = parse_val(t)
if val is not None:
found.append(val)
if len(found) == 2:
break
return found
# Pass 0:
# 1. find a header line with pipes to derive alignment hint
# 2. calculate max content width to ensure right-most alignment
max_content_width = 0
for line in lines:
if line.strip() == "```":
in_code = not in_code
continue
if not in_code:
continue
# Skip footnotes/meta for width calculation
if re.match(r'^\s*[¹²³⁴⁵⁶⁷⁸⁹⁰]', line) or re.search(r'need\s*>?=\s*\d+\s+samples', line):
continue
if not line.strip() or line.strip().startswith(('goos:', 'goarch:', 'pkg:', 'cpu:')):
continue
# Header lines are handled separately in Pass 1
if '│' in line and ('vs base' in line or 'old' in line or 'new' in line):
continue
# It's likely a data line
# Check if it has an existing percentage we might move/align
curr_line = strip_worker_suffix(line).rstrip()
pct_match = re.search(r'([+-]?\d+\.\d+)%', curr_line)
if pct_match:
# If we are going to realign this, we count width up to the percentage
w = len(curr_line[:pct_match.start()].rstrip())
else:
w = len(curr_line)
if w > max_content_width:
max_content_width = w
# Calculate global alignment target for Diff column
# Ensure target column is beyond the longest line with some padding
diff_col_start = max_content_width + 4
# Calculate right boundary (pipe) position
# Diff column width ~12 chars (e.g. "+100.00% 🚀")
right_boundary = diff_col_start + 14
# Reset code fence tracking state for Pass 1
in_code = False
for line in lines:
if line.strip() == "```":
in_code = not in_code
processed_lines.append(line)
continue
if not in_code:
processed_lines.append(line)
continue
# footnotes keep untouched
if re.match(r'^\s*[¹²³⁴⁵⁶⁷⁸⁹⁰]', line) or re.search(r'need\s*>?=\s*\d+\s+samples', line):
processed_lines.append(line)
continue
# header lines: ensure last column labeled Diff and force alignment
if '│' in line and ('vs base' in line or 'old' in line or 'new' in line):
# Strip trailing pipe and whitespace
stripped_header = line.rstrip().rstrip('│').rstrip()
# If "vs base" is present, ensure we don't duplicate "Diff" if it's already there
# But we want to enforce OUR alignment, so we might strip existing Diff
stripped_header = re.sub(r'\s+Diff\s*$', '', stripped_header, flags=re.IGNORECASE)
stripped_header = re.sub(r'\s+Delta\b', '', stripped_header, flags=re.IGNORECASE)
# Pad to diff_col_start
if len(stripped_header) < diff_col_start:
new_header = stripped_header + " " * (diff_col_start - len(stripped_header))
else:
new_header = stripped_header + " "
# Add Diff column header if it's the second header row (vs base)
if 'vs base' in line:
new_header += "Diff"
# Add closing pipe at the right boundary
current_len = len(new_header)
if current_len < right_boundary:
new_header += " " * (right_boundary - current_len)
new_header += "│"
processed_lines.append(new_header)
continue
# non-data meta lines
if not line.strip() or line.strip().startswith(('goos:', 'goarch:', 'pkg:')):
processed_lines.append(line)
continue
line = strip_worker_suffix(line)
tokens = line.split()
if not tokens:
processed_lines.append(line)
continue
numbers = extract_two_numbers(tokens)
pct_match = re.search(r'([+-]?\d+\.\d+)%', line)
# Helper to align and append
def append_aligned(left_part, content):
if len(left_part) < diff_col_start:
aligned = left_part + " " * (diff_col_start - len(left_part))
else:
aligned = left_part + " "
# Ensure content doesn't exceed right boundary (visual check only, we don't truncate)
# But users asked not to exceed header pipe.
# Header pipe is at right_boundary.
# Content starts at diff_col_start.
# So content length should be <= right_boundary - diff_col_start
return f"{aligned}{content}"
# Special handling for geomean when values missing or zero
is_geomean = tokens[0] == "geomean"
if is_geomean and (len(numbers) < 2 or any(v == 0 for v in numbers)) and not pct_match:
leading = re.match(r'^\s*', line).group(0)
left = f"{leading}geomean"
processed_lines.append(append_aligned(left, "n/a (has zero)"))
continue
# when both values are zero, force diff = 0 and align
if len(numbers) == 2 and numbers[0] == 0 and numbers[1] == 0:
diff_val = 0.0
icon = get_icon(diff_val)
left = line.rstrip()
processed_lines.append(append_aligned(left, f"{diff_val:+.2f}% {icon}"))
continue
# recompute diff when we have two numeric values
if len(numbers) == 2 and numbers[0] != 0:
diff_val = (numbers[1] - numbers[0]) / numbers[0] * 100
icon = get_icon(diff_val)
left = line
if pct_match:
left = line[:pct_match.start()].rstrip()
else:
left = line.rstrip()
processed_lines.append(append_aligned(left, f"{diff_val:+.2f}% {icon}"))
continue
# fallback: align existing percentage to Diff column and (re)append icon
if pct_match:
try:
pct_val = float(pct_match.group(1))
icon = get_icon(pct_val)
left = line[:pct_match.start()].rstrip()
suffix = line[pct_match.end():]
# Remove any existing icon after the percentage to avoid duplicates
suffix = re.sub(r'\s*(🐌|🚀|➡️)', '', suffix)
processed_lines.append(append_aligned(left, f"{pct_val:+.2f}% {icon}{suffix}"))
except ValueError:
processed_lines.append(line)
continue
# If we cannot parse numbers or percentages, keep the original (only worker suffix stripped)
processed_lines.append(line)
p.write_text("\n".join(processed_lines) + "\n", encoding="utf-8")
except Exception as e:
print(f"Error post-processing comparison.md: {e}")
sys.exit(1)
================================================
FILE: .github/scripts/download_artifact.js
================================================
module.exports = async ({github, context, core}) => {
try {
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const matchArtifact = artifacts.data.artifacts.find((artifact) => {
return artifact.name == "benchmark-results";
});
if (!matchArtifact) {
core.setFailed("No artifact named 'benchmark-results' found.");
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
const fs = require('fs');
const path = require('path');
const workspace = process.env.GITHUB_WORKSPACE;
fs.writeFileSync(path.join(workspace, 'benchmark-results.zip'), Buffer.from(download.data));
} catch (error) {
core.setFailed(`Failed to download artifact: ${error.message}`);
}
};
================================================
FILE: .github/scripts/post_comment.js
================================================
module.exports = async ({github, context, core}) => {
const fs = require('fs');
// Validate pr_number.txt
if (!fs.existsSync('pr_number.txt')) {
core.setFailed("Required artifact file 'pr_number.txt' was not found in the workspace.");
return;
}
const prNumberContent = fs.readFileSync('pr_number.txt', 'utf8').trim();
const issue_number = parseInt(prNumberContent, 10);
if (!Number.isFinite(issue_number) || issue_number <= 0) {
core.setFailed('Invalid PR number in pr_number.txt: "' + prNumberContent + '"');
return;
}
// Validate comparison.md
if (!fs.existsSync('comparison.md')) {
core.setFailed("Required artifact file 'comparison.md' was not found in the workspace.");
return;
}
let comparison;
try {
comparison = fs.readFileSync('comparison.md', 'utf8');
} catch (error) {
core.setFailed("Failed to read 'comparison.md': " + error.message);
return;
}
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Benchmark Comparison')
);
const footer = '🤖 This comment will be automatically updated with the latest benchmark results.';
const commentBody = `${comparison}\n\n${footer}`;
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: commentBody
});
}
};
================================================
FILE: .github/semantic.yml
================================================
# Always validate the PR title AND all the commits
titleAndCommits: true
================================================
FILE: .github/workflows/comment.yml
================================================
name: Post Benchmark Comment
on:
workflow_run:
workflows: ["Performance Comparison for Pull Requests"]
types:
- completed
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: 'Download artifact'
uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/download_artifact.js')
await script({github, context, core})
- name: 'Unzip artifact'
run: unzip benchmark-results.zip
- name: 'Post comment'
uses: actions/github-script@v7
with:
script: |
const script = require('./.github/scripts/post_comment.js')
await script({github, context, core})
================================================
FILE: .github/workflows/default.yml
================================================
name: Build
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go: ['1.21']
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Run go test
run: make test
benchmark:
runs-on: ubuntu-latest
strategy:
matrix:
go: ['1.21']
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Run go test bench
run: make benchmark
semantic-release:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20
- name: Run semantic-release
if: github.repository == 'casbin/casbin' && github.event_name == 'push'
run: make release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/golangci-lint.yml
================================================
name: golangci-lint
on:
push:
branches:
- master
- main
pull_request:
jobs:
golangci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.56.2
================================================
FILE: .github/workflows/performance-pr.yml
================================================
name: Performance Comparison for Pull Requests
on:
pull_request:
branches: [master]
jobs:
benchmark-pr:
name: Performance benchmark comparison
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 'stable'
# Save commit SHAs for display
- name: Save commit info
id: commits
run: |
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
echo "base_short=${BASE_SHA:0:7}" >> $GITHUB_OUTPUT
echo "head_short=${HEAD_SHA:0:7}" >> $GITHUB_OUTPUT
# Run benchmark on PR branch
- name: Run benchmark on PR branch
run: |
go test -bench '.' -benchtime=2s -benchmem ./... | tee pr-bench.txt
# Checkout base branch and run benchmark
- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
clean: false
path: base
- name: Run benchmark on base branch
working-directory: base
run: |
go test -bench '.' -benchtime=2s -benchmem ./... | \
tee ../base-bench.txt
# Install benchstat for comparison
- name: Install benchstat
run: go install golang.org/x/perf/cmd/benchstat@latest
# Compare benchmarks using benchstat
- name: Compare benchmarks with benchstat
id: benchstat
run: |
cat > comparison.md << 'EOF'
## Benchmark Comparison
Comparing base branch (`${{ steps.commits.outputs.base_short }}`)
vs PR branch (`${{ steps.commits.outputs.head_short }}`)
```
EOF
benchstat base-bench.txt pr-bench.txt >> comparison.md || true
echo '```' >> comparison.md
# Post-process to append percentage + emoji column (🚀 faster < -10%, 🐌 slower > +10%, otherwise ➡️)
if [ ! -f comparison.md ]; then
echo "comparison.md not found after benchstat." >&2
exit 1
fi
python3 .github/scripts/benchmark_formatter.py
# Save PR number
- name: Save PR number
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
if [ -z "$PR_NUMBER" ]; then
echo "Error: Pull request number is not available in event payload." >&2
exit 1
fi
echo "$PR_NUMBER" > pr_number.txt
# Upload benchmark results
- name: Upload benchmark results
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: |
comparison.md
pr_number.txt
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.idea/
*.iml
# vendor files
vendor
================================================
FILE: .golangci.yml
================================================
# Based on https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322
# This code is licensed under the terms of the MIT license https://opensource.org/license/mit
# Copyright (c) 2021 Marat Reymers
## Golden config for golangci-lint v1.56.2
#
# This is the best config for golangci-lint based on my experience and opinion.
# It is very strict, but not extremely strict.
# Feel free to adapt and change it for your needs.
run:
# Timeout for analysis, e.g. 30s, 5m.
# Default: 1m
timeout: 3m
# This file contains only configs which differ from defaults.
# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
linters-settings:
cyclop:
# The maximal code complexity to report.
# Default: 10
max-complexity: 30
# The maximal average package complexity.
# If it's higher than 0.0 (float) the check is enabled
# Default: 0.0
package-average: 10.0
errcheck:
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
# Such cases aren't reported by default.
# Default: false
check-type-assertions: true
exhaustive:
# Program elements to check for exhaustiveness.
# Default: [ switch ]
check:
- switch
- map
exhaustruct:
# List of regular expressions to exclude struct packages and their names from checks.
# Regular expressions must match complete canonical struct package/name/structname.
# Default: []
exclude:
# std libs
- "^net/http.Client$"
- "^net/http.Cookie$"
- "^net/http.Request$"
- "^net/http.Response$"
- "^net/http.Server$"
- "^net/http.Transport$"
- "^net/url.URL$"
- "^os/exec.Cmd$"
- "^reflect.StructField$"
# public libs
- "^github.com/Shopify/sarama.Config$"
- "^github.com/Shopify/sarama.ProducerMessage$"
- "^github.com/mitchellh/mapstructure.DecoderConfig$"
- "^github.com/prometheus/client_golang/.+Opts$"
- "^github.com/spf13/cobra.Command$"
- "^github.com/spf13/cobra.CompletionOptions$"
- "^github.com/stretchr/testify/mock.Mock$"
- "^github.com/testcontainers/testcontainers-go.+Request$"
- "^github.com/testcontainers/testcontainers-go.FromDockerfile$"
- "^golang.org/x/tools/go/analysis.Analyzer$"
- "^google.golang.org/protobuf/.+Options$"
- "^gopkg.in/yaml.v3.Node$"
funlen:
# Checks the number of lines in a function.
# If lower than 0, disable the check.
# Default: 60
lines: 100
# Checks the number of statements in a function.
# If lower than 0, disable the check.
# Default: 40
statements: 50
# Ignore comments when counting lines.
# Default false
ignore-comments: true
gocognit:
# Minimal code complexity to report.
# Default: 30 (but we recommend 10-20)
min-complexity: 20
gocritic:
# Settings passed to gocritic.
# The settings key is the name of a supported gocritic checker.
# The list of supported checkers can be find in https://go-critic.github.io/overview.
settings:
captLocal:
# Whether to restrict checker to params only.
# Default: true
paramsOnly: false
underef:
# Whether to skip (*x).method() calls where x is a pointer receiver.
# Default: true
skipRecvDeref: false
gomnd:
# List of function patterns to exclude from analysis.
# Values always ignored: `time.Date`,
# `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`,
# `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`.
# Default: []
ignored-functions:
- flag.Arg
- flag.Duration.*
- flag.Float.*
- flag.Int.*
- flag.Uint.*
- os.Chmod
- os.Mkdir.*
- os.OpenFile
- os.WriteFile
- prometheus.ExponentialBuckets.*
- prometheus.LinearBuckets
gomodguard:
blocked:
# List of blocked modules.
# Default: []
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules"
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: "satori's package is not maintained"
- github.com/gofrs/uuid:
recommendations:
- github.com/gofrs/uuid/v5
reason: "gofrs' package was not go module before v5"
govet:
# Enable all analyzers.
# Default: false
enable-all: true
# Disable analyzers by name.
# Run `go tool vet help` to see all analyzers.
# Default: []
disable:
- fieldalignment # too strict
# Settings per analyzer.
settings:
shadow:
# Whether to be strict about shadowing; can be noisy.
# Default: false
#strict: true
inamedparam:
# Skips check for interface methods with only a single parameter.
# Default: false
skip-single-param: true
nakedret:
# Make an issue if func has more lines of code than this setting, and it has naked returns.
# Default: 30
max-func-lines: 0
nolintlint:
# Exclude following linters from requiring an explanation.
# Default: []
allow-no-explanation: [ funlen, gocognit, lll ]
# Enable to require an explanation of nonzero length after each nolint directive.
# Default: false
require-explanation: true
# Enable to require nolint directives to mention the specific linter being suppressed.
# Default: false
require-specific: true
perfsprint:
# Optimizes into strings concatenation.
# Default: true
strconcat: false
rowserrcheck:
# database/sql is always checked
# Default: []
packages:
- github.com/jmoiron/sqlx
tenv:
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
# Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
# Default: false
all: true
stylecheck:
# STxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"]
checks: ["all", "-ST1003"]
revive:
rules:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
- name: unused-parameter
disabled: true
linters:
disable-all: true
enable:
## enabled by default
#- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
- gosimple # specializes in simplifying a code
- govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # detects when assignments to existing variables are not used
- staticcheck # is a go vet on steroids, applying a ton of static analysis checks
- typecheck # like the front-end of a Go compiler, parses and type-checks Go code
- unused # checks for unused constants, variables, functions and types
## disabled by default
- asasalint # checks for pass []any as any in variadic func(...any)
- asciicheck # checks that your code does not contain non-ASCII identifiers
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- cyclop # checks function and package cyclomatic complexity
- dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
#- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
- execinquery # checks query string in Query function which reads your Go src files and warning it finds
- exhaustive # checks exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
#- forbidigo # forbids identifiers
- funlen # tool for detection of long functions
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
#- gochecknoglobals # checks that no global variables exist
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types"
#- gocognit # computes and checks the cognitive complexity of functions
#- goconst # finds repeated strings that could be replaced by a constant
#- gocritic # provides diagnostics that check for bugs, performance and style issues
- gocyclo # computes and checks the cyclomatic complexity of functions
- godot # checks if comments end in a period
- goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt
#- gomnd # detects magic numbers
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
- goprintffuncname # checks that printf-like functions are named with f at the end
- gosec # inspects source code for security problems
#- lll # reports long lines
- loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)
- makezero # finds slice declarations with non-zero initial length
- mirror # reports wrong mirror patterns of bytes/strings usage
- musttag # enforces field tags in (un)marshaled structs
- nakedret # finds naked returns in functions greater than a specified function length
- nestif # reports deeply nested if statements
- nilerr # finds the code that returns nil even if it checks that the error is not nil
#- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- noctx # finds sending http request without context.Context
- nolintlint # reports ill-formed or insufficient nolint directives
#- nonamedreturns # reports all named returns
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
#- perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative
- predeclared # finds code that shadows one of Go's predeclared identifiers
- promlinter # checks Prometheus metrics naming via promlint
- protogetter # reports direct reads from proto message fields when getters should be used
- reassign # checks that package variables are not reassigned
- revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint
- rowserrcheck # checks whether Err of rows is checked successfully
- sloglint # ensure consistent code style when using log/slog
- spancheck # checks for mistakes with OpenTelemetry/Census spans
- sqlclosecheck # checks that sql.Rows and sql.Stmt are closed
- stylecheck # is a replacement for golint
- tenv # detects using os.Setenv instead of t.Setenv since Go1.17
- testableexamples # checks if examples are testable (have an expected output)
- testifylint # checks usage of github.com/stretchr/testify
#- testpackage # makes you use a separate _test package
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
- unconvert # removes unnecessary type conversions
#- unparam # reports unused function parameters
- usestdlibvars # detects the possibility to use variables/constants from the Go standard library
- wastedassign # finds wasted assignment statements
- whitespace # detects leading and trailing whitespace
## you may want to enable
#- decorder # checks declaration order and count of types, constants, variables and functions
#- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized
#- gci # controls golang package import order and makes it always deterministic
#- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega
#- godox # detects FIXME, TODO and other comment keywords
#- goheader # checks is file header matches to pattern
#- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters
#- interfacebloat # checks the number of methods inside an interface
#- ireturn # accept interfaces, return concrete types
#- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated
#- tagalign # checks that struct tags are well aligned
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
#- wrapcheck # checks that errors returned from external packages are wrapped
#- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event
## disabled
#- containedctx # detects struct contained context.Context field
#- contextcheck # [too many false positives] checks the function whether use a non-inherited context
#- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages
#- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
#- dupword # [useless without config] checks for duplicate words in the source code
#- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted
#- forcetypeassert # [replaced by errcheck] finds forced type assertions
#- goerr113 # [too strict] checks the errors handling expressions
#- gofmt # [replaced by goimports] checks whether code was gofmt-ed
#- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed
#- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase
#- grouper # analyzes expression groups
#- importas # enforces consistent import aliases
#- maintidx # measures the maintainability index of each function
#- misspell # [useless] finds commonly misspelled English words in comments
#- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity
#- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test
#- tagliatelle # checks the struct tags
#- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers
#- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
## deprecated
#- deadcode # [deprecated, replaced by unused] finds unused code
#- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized
#- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
#- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible
#- interfacer # [deprecated] suggests narrower interface types
#- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted
#- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name
#- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs
#- structcheck # [deprecated, replaced by unused] finds unused struct fields
#- varcheck # [deprecated, replaced by unused] finds unused global variables and constants
issues:
# Maximum count of issues with the same text.
# Set to 0 to disable.
# Default: 3
max-same-issues: 50
exclude-rules:
- source: "(noinspection|TODO)"
linters: [ godot ]
- source: "//noinspection"
linters: [ gocritic ]
- path: "_test\\.go"
linters:
- bodyclose
- dupl
- funlen
- goconst
- gosec
- noctx
- wrapcheck
# TODO: remove after PR is released https://github.com/golangci/golangci-lint/pull/4386
- text: "fmt.Sprintf can be replaced with string addition"
linters: [ perfsprint ]
================================================
FILE: .releaserc.json
================================================
{
"debug": true,
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
{
"name": "beta",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute
The following is a set of guidelines for contributing to casbin and its libraries, which are hosted at [casbin organization at Github](https://github.com/casbin).
This project adheres to the [Contributor Covenant 1.2.](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct.html) By participating, you are expected to uphold this code. Please report unacceptable behavior to info@casbin.com.
## Questions
- We do our best to have an [up-to-date documentation](https://casbin.org/docs/overview)
- [Stack Overflow](https://stackoverflow.com) is the best place to start if you have a question. Please use the [casbin tag](https://stackoverflow.com/tags/casbin/info) we are actively monitoring. We encourage you to use Stack Overflow specially for Modeling Access Control Problems, in order to build a shared knowledge base.
- You can also join our [Discord](https://discord.gg/S5UjpzGZjN).
## Reporting issues
Reporting issues are a great way to contribute to the project. We are perpetually grateful about a well-written, through bug report.
Before raising a new issue, check our [issue list](https://github.com/casbin/casbin/issues) to determine if it already contains the problem that you are facing.
A good bug report shouldn't leave others needing to chase you for more information. Please be as detailed as possible. The following questions might serve as a template for writing a detailed report:
What were you trying to achieve?
What are the expected results?
What are the received results?
What are the steps to reproduce the issue?
In what environment did you encounter the issue?
Feature requests can also be submitted as issues.
## Pull requests
Good pull requests (e.g. patches, improvements, new features) are a fantastic help. They should remain focused in scope and avoid unrelated commits.
Please ask first before embarking on any significant pull request (e.g. implementing new features, refactoring code etc.), otherwise you risk spending a lot of time working on something that the maintainers might not want to merge into the project.
First add an issue to the project to discuss the improvement. Please adhere to the coding conventions used throughout the project. If in doubt, consult the [Effective Go style guide](https://golang.org/doc/effective_go.html).
================================================
FILE: DISCLAIMER
================================================
Apache Casbin(Incubating) is an effort undergoing incubation at the Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC.
Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects.
While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
SHELL = /bin/bash
export PATH := $(shell yarn global bin):$(PATH)
default: lint test
test:
go test -race -v ./...
benchmark:
go test -bench=.
lint:
golangci-lint run --verbose
release:
npx semantic-release@v19.0.2
================================================
FILE: README.md
================================================
Casbin
====
[](https://goreportcard.com/report/github.com/casbin/casbin)
[](https://github.com/casbin/casbin/actions/workflows/default.yml)
[](https://coveralls.io/github/casbin/casbin?branch=master)
[](https://pkg.go.dev/github.com/casbin/casbin/v2)
[](https://github.com/casbin/casbin/releases/latest)
[](https://discord.gg/S5UjpzGZjN)
[](https://sourcegraph.com/github.com/casbin/casbin?badge)
**News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: https://casbin.org/editor/

Casbin is a powerful and efficient open-source access control library for Golang projects. It provides support for enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model).
## All the languages supported by Casbin:
| [](https://github.com/casbin/casbin) | [](https://github.com/casbin/jcasbin) | [](https://github.com/casbin/node-casbin) | [](https://github.com/php-casbin/php-casbin) |
|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| [Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin) | [PHP-Casbin](https://github.com/php-casbin/php-casbin) |
| production-ready | production-ready | production-ready | production-ready |
| [](https://github.com/casbin/pycasbin) | [](https://github.com/casbin-net/Casbin.NET) | [](https://github.com/casbin/casbin-cpp) | [](https://github.com/casbin/casbin-rs) |
|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| [PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin-CPP](https://github.com/casbin/casbin-cpp) | [Casbin-RS](https://github.com/casbin/casbin-rs) |
| production-ready | production-ready | production-ready | production-ready |
## Table of contents
- [Supported models](#supported-models)
- [How it works?](#how-it-works)
- [Features](#features)
- [Installation](#installation)
- [Documentation](#documentation)
- [Online editor](#online-editor)
- [Tutorials](#tutorials)
- [Get started](#get-started)
- [Policy management](#policy-management)
- [Policy persistence](#policy-persistence)
- [Policy consistence between multiple nodes](#policy-consistence-between-multiple-nodes)
- [Role manager](#role-manager)
- [Benchmarks](#benchmarks)
- [Examples](#examples)
- [Middlewares](#middlewares)
- [Our adopters](#our-adopters)
## Supported models
1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list)
2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)**
3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins.
3. **ACL without resources**: some scenarios may target for a type of resources instead of an individual resource by using permissions like ``write-article``, ``read-log``. It doesn't control the access to a specific article or log.
4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)**
5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time.
6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants.
7. **[ABAC (Attribute-Based Access Control)](https://en.wikipedia.org/wiki/Attribute-Based_Access_Control)**: syntax sugar like ``resource.Owner`` can be used to get the attribute for a resource.
8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``.
9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow.
10. **Priority**: the policy rules can be prioritized like firewall rules.
## How it works?
In Casbin, an access control model is abstracted into a CONF file based on the **PERM metamodel (Policy, Effect, Request, Matchers)**. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules.
The most basic and simplest model in Casbin is ACL. ACL's model CONF is:
```ini
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
```
An example policy for ACL model is like:
```
p, alice, data1, read
p, bob, data2, write
```
It means:
- alice can read data1
- bob can write data2
We also support multi-line mode by appending '\\' in the end:
```ini
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj \
&& r.act == p.act
```
Further more, if you are using ABAC, you can try operator `in` like following in Casbin **golang** edition (jCasbin and Node-Casbin are not supported yet):
```ini
# Matchers
[matchers]
m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')
```
But you **SHOULD** make sure that the length of the array is **MORE** than **1**, otherwise there will cause it to panic.
For more operators, you may take a look at [govaluate](https://github.com/casbin/govaluate)
## Features
What Casbin does:
1. enforce the policy in the classic ``{subject, object, action}`` form or a customized form as you defined, both allow and deny authorizations are supported.
2. handle the storage of the access control model and its policy.
3. manage the role-user mappings and role-role mappings (aka role hierarchy in RBAC).
4. support built-in superuser like ``root`` or ``administrator``. A superuser can do anything without explicit permissions.
5. multiple built-in operators to support the rule matching. For example, ``keyMatch`` can map a resource key ``/foo/bar`` to the pattern ``/foo*``.
What Casbin does NOT do:
1. authentication (aka verify ``username`` and ``password`` when a user logs in)
2. manage the list of users or roles. I believe it's more convenient for the project itself to manage these entities. Users usually have their passwords, and Casbin is not designed as a password container. However, Casbin stores the user-role mapping for the RBAC scenario.
## Installation
```
go get github.com/casbin/casbin/v3
```
## Documentation
https://casbin.org/docs/overview
## Online editor
You can also use the online editor (https://casbin.org/editor/) to write your Casbin model and policy in your web browser. It provides functionality such as ``syntax highlighting`` and ``code completion``, just like an IDE for a programming language.
## Tutorials
https://casbin.org/docs/tutorials
## Get started
1. New a Casbin enforcer with a model file and a policy file:
```go
e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
```
Note: you can also initialize an enforcer with policy in DB instead of file, see [Policy-persistence](#policy-persistence) section for details.
2. Add an enforcement hook into your code right before the access happens:
```go
sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.
if res, _ := e.Enforce(sub, obj, act); res {
// permit alice to read data1
} else {
// deny the request, show an error
}
```
3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below:
```go
roles, _ := e.GetImplicitRolesForUser(sub)
```
See [Policy management APIs](#policy-management) for more usage.
## Policy management
Casbin provides two sets of APIs to manage permissions:
- [Management API](https://casbin.org/docs/management-api): the primitive API that provides full support for Casbin policy management.
- [RBAC API](https://casbin.org/docs/rbac-api): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code.
We also provide a [web-based UI](https://casbin.org/docs/admin-portal) for model management and policy management:


## Policy persistence
https://casbin.org/docs/adapters
## Policy consistence between multiple nodes
https://casbin.org/docs/watchers
## Role manager
https://casbin.org/docs/role-managers
## Benchmarks
https://casbin.org/docs/benchmark
## Examples
| Model | Model file | Policy file |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| ACL | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) |
| ACL with superuser | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) |
| ACL without users | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv) |
| ACL without resources | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv) |
| RBAC | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv) |
| RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) |
| RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv) |
| ABAC | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf) | N/A |
| RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv) |
| Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv) |
| Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv) |
## Middlewares
Authz middlewares for web frameworks: https://casbin.org/docs/middlewares
## Our adopters
https://casbin.org/docs/adopters
## How to Contribute
Please read the [contributing guide](CONTRIBUTING.md).
## Contributors
This project exists thanks to all the people who contribute.
## Star History
[](https://star-history.com/#casbin/casbin&Date)
## License
This project is licensed under the [Apache 2.0 license](LICENSE).
## Contact
If you have any issues or feature requests, please contact us. PR is welcomed.
- https://github.com/casbin/casbin/issues
- https://discord.gg/S5UjpzGZjN
================================================
FILE: abac_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"encoding/json"
"fmt"
"testing"
"github.com/casbin/casbin/v3/util"
)
type testResource struct {
Name string
Owner string
}
func newTestResource(name string, owner string) testResource {
r := testResource{}
r.Name = name
r.Owner = owner
return r
}
func TestABACModel(t *testing.T) {
e, _ := NewEnforcer("examples/abac_model.conf")
data1 := newTestResource("data1", "alice")
data2 := newTestResource("data2", "bob")
testEnforce(t, e, "alice", data1, "read", true)
testEnforce(t, e, "alice", data1, "write", true)
testEnforce(t, e, "alice", data2, "read", false)
testEnforce(t, e, "alice", data2, "write", false)
testEnforce(t, e, "bob", data1, "read", false)
testEnforce(t, e, "bob", data1, "write", false)
testEnforce(t, e, "bob", data2, "read", true)
testEnforce(t, e, "bob", data2, "write", true)
}
func TestABACMapRequest(t *testing.T) {
e, _ := NewEnforcer("examples/abac_model.conf")
data1 := map[string]interface{}{
"Name": "data1",
"Owner": "alice",
}
data2 := map[string]interface{}{
"Name": "data2",
"Owner": "bob",
}
testEnforce(t, e, "alice", data1, "read", true)
testEnforce(t, e, "alice", data1, "write", true)
testEnforce(t, e, "alice", data2, "read", false)
testEnforce(t, e, "alice", data2, "write", false)
testEnforce(t, e, "bob", data1, "read", false)
testEnforce(t, e, "bob", data1, "write", false)
testEnforce(t, e, "bob", data2, "read", true)
testEnforce(t, e, "bob", data2, "write", true)
}
func TestABACTypes(t *testing.T) {
e, _ := NewEnforcer("examples/abac_model.conf")
matcher := `"moderator" IN r.sub.Roles && r.sub.Enabled == true && r.sub.Age >= 21 && r.sub.Name != "foo"`
e.GetModel()["m"]["m"].Value = util.RemoveComments(util.EscapeAssertion(matcher))
structRequest := struct {
Roles []interface{}
Enabled bool
Age int
Name string
}{
Roles: []interface{}{"user", "moderator"},
Enabled: true,
Age: 30,
Name: "alice",
}
testEnforce(t, e, structRequest, "", "", true)
mapRequest := map[string]interface{}{
"Roles": []interface{}{"user", "moderator"},
"Enabled": true,
"Age": 30,
"Name": "alice",
}
testEnforce(t, e, mapRequest, nil, "", true)
e.EnableAcceptJsonRequest(true)
jsonRequest, _ := json.Marshal(mapRequest)
testEnforce(t, e, string(jsonRequest), "", "", true)
}
func TestABACJsonRequest(t *testing.T) {
e, _ := NewEnforcer("examples/abac_model.conf")
e.EnableAcceptJsonRequest(true)
data1Json := `{ "Name": "data1", "Owner": "alice"}`
data2Json := `{ "Name": "data2", "Owner": "bob"}`
testEnforce(t, e, "alice", data1Json, "read", true)
testEnforce(t, e, "alice", data1Json, "write", true)
testEnforce(t, e, "alice", data2Json, "read", false)
testEnforce(t, e, "alice", data2Json, "write", false)
testEnforce(t, e, "bob", data1Json, "read", false)
testEnforce(t, e, "bob", data1Json, "write", false)
testEnforce(t, e, "bob", data2Json, "read", true)
testEnforce(t, e, "bob", data2Json, "write", true)
e, _ = NewEnforcer("examples/abac_not_using_policy_model.conf", "examples/abac_rule_effect_policy.csv")
e.EnableAcceptJsonRequest(true)
testEnforce(t, e, "alice", data1Json, "read", true)
testEnforce(t, e, "alice", data1Json, "write", true)
testEnforce(t, e, "alice", data2Json, "read", false)
testEnforce(t, e, "alice", data2Json, "write", false)
e, _ = NewEnforcer("examples/abac_rule_model.conf", "examples/abac_rule_policy.csv")
e.EnableAcceptJsonRequest(true)
sub1Json := `{"Name": "alice", "Age": 16}`
sub2Json := `{"Name": "alice", "Age": 20}`
sub3Json := `{"Name": "alice", "Age": 65}`
testEnforce(t, e, sub1Json, "/data1", "read", false)
testEnforce(t, e, sub1Json, "/data2", "read", false)
testEnforce(t, e, sub1Json, "/data1", "write", false)
testEnforce(t, e, sub1Json, "/data2", "write", true)
testEnforce(t, e, sub2Json, "/data1", "read", true)
testEnforce(t, e, sub2Json, "/data2", "read", false)
testEnforce(t, e, sub2Json, "/data1", "write", false)
testEnforce(t, e, sub2Json, "/data2", "write", true)
testEnforce(t, e, sub3Json, "/data1", "read", true)
testEnforce(t, e, sub3Json, "/data2", "read", false)
testEnforce(t, e, sub3Json, "/data1", "write", false)
testEnforce(t, e, sub3Json, "/data2", "write", false)
}
type testSub struct {
Name string
Age int
}
func newTestSubject(name string, age int) testSub {
s := testSub{}
s.Name = name
s.Age = age
return s
}
func TestABACNotUsingPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/abac_not_using_policy_model.conf", "examples/abac_rule_effect_policy.csv")
data1 := newTestResource("data1", "alice")
data2 := newTestResource("data2", "bob")
testEnforce(t, e, "alice", data1, "read", true)
testEnforce(t, e, "alice", data1, "write", true)
testEnforce(t, e, "alice", data2, "read", false)
testEnforce(t, e, "alice", data2, "write", false)
}
func TestABACPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/abac_rule_model.conf", "examples/abac_rule_policy.csv")
m := e.GetModel()
for sec, ast := range m {
fmt.Println(sec)
for ptype, p := range ast {
fmt.Println(ptype, p)
}
}
sub1 := newTestSubject("alice", 16)
sub2 := newTestSubject("alice", 20)
sub3 := newTestSubject("alice", 65)
testEnforce(t, e, sub1, "/data1", "read", false)
testEnforce(t, e, sub1, "/data2", "read", false)
testEnforce(t, e, sub1, "/data1", "write", false)
testEnforce(t, e, sub1, "/data2", "write", true)
testEnforce(t, e, sub2, "/data1", "read", true)
testEnforce(t, e, sub2, "/data2", "read", false)
testEnforce(t, e, sub2, "/data1", "write", false)
testEnforce(t, e, sub2, "/data2", "write", true)
testEnforce(t, e, sub3, "/data1", "read", true)
testEnforce(t, e, sub3, "/data2", "read", false)
testEnforce(t, e, sub3, "/data1", "write", false)
testEnforce(t, e, sub3, "/data2", "write", false)
}
================================================
FILE: ai_api.go
================================================
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
)
// AIConfig contains configuration for AI API calls.
type AIConfig struct {
// Endpoint is the API endpoint (e.g., "https://api.openai.com/v1/chat/completions")
Endpoint string
// APIKey is the authentication key for the API
APIKey string
// Model is the model to use (e.g., "gpt-3.5-turbo", "gpt-4")
Model string
// Timeout for API requests (default: 30s)
Timeout time.Duration
}
// aiMessage represents a message in the OpenAI chat format.
type aiMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
// aiChatRequest represents the request to OpenAI chat completions API.
type aiChatRequest struct {
Model string `json:"model"`
Messages []aiMessage `json:"messages"`
}
// aiChatResponse represents the response from OpenAI chat completions API.
type aiChatResponse struct {
Choices []struct {
Message aiMessage `json:"message"`
} `json:"choices"`
Error *struct {
Message string `json:"message"`
} `json:"error,omitempty"`
}
// SetAIConfig sets the configuration for AI API calls.
func (e *Enforcer) SetAIConfig(config AIConfig) {
if config.Timeout == 0 {
config.Timeout = 30 * time.Second
}
e.aiConfig = config
}
// Explain returns an AI-generated explanation of why Enforce returned a particular result.
// It calls the configured OpenAI-compatible API to generate a natural language explanation.
func (e *Enforcer) Explain(rvals ...interface{}) (string, error) {
if e.aiConfig.Endpoint == "" {
return "", errors.New("AI config not set, use SetAIConfig first")
}
// Get enforcement result and matched rules
result, matchedRules, err := e.EnforceEx(rvals...)
if err != nil {
return "", fmt.Errorf("failed to enforce: %w", err)
}
// Build context for AI
explainContext := e.buildExplainContext(rvals, result, matchedRules)
// Call AI API
explanation, err := e.callAIAPI(explainContext)
if err != nil {
return "", fmt.Errorf("failed to get AI explanation: %w", err)
}
return explanation, nil
}
// buildExplainContext builds the context string for AI explanation.
func (e *Enforcer) buildExplainContext(rvals []interface{}, result bool, matchedRules []string) string {
var sb strings.Builder
// Add request information
sb.WriteString("Authorization Request:\n")
sb.WriteString(fmt.Sprintf("Subject: %v\n", rvals[0]))
if len(rvals) > 1 {
sb.WriteString(fmt.Sprintf("Object: %v\n", rvals[1]))
}
if len(rvals) > 2 {
sb.WriteString(fmt.Sprintf("Action: %v\n", rvals[2]))
}
sb.WriteString(fmt.Sprintf("\nEnforcement Result: %v\n", result))
// Add matched rules
if len(matchedRules) > 0 {
sb.WriteString("\nMatched Policy Rules:\n")
for _, rule := range matchedRules {
sb.WriteString(fmt.Sprintf("- %s\n", rule))
}
} else {
sb.WriteString("\nNo policy rules matched.\n")
}
// Add model information
sb.WriteString("\nAccess Control Model:\n")
if m, ok := e.model["m"]; ok {
for key, ast := range m {
sb.WriteString(fmt.Sprintf("Matcher (%s): %s\n", key, ast.Value))
}
}
if eff, ok := e.model["e"]; ok {
for key, ast := range eff {
sb.WriteString(fmt.Sprintf("Effect (%s): %s\n", key, ast.Value))
}
}
// Add all policies
policies, _ := e.GetPolicy()
if len(policies) > 0 {
sb.WriteString("\nAll Policy Rules:\n")
for _, policy := range policies {
sb.WriteString(fmt.Sprintf("- %s\n", strings.Join(policy, ", ")))
}
}
return sb.String()
}
// callAIAPI calls the configured AI API to get an explanation.
func (e *Enforcer) callAIAPI(explainContext string) (string, error) {
// Prepare the request
messages := []aiMessage{
{
Role: "system",
Content: "You are an expert in access control and authorization systems. " +
"Explain why an authorization request was allowed or denied based on the " +
"provided access control model, policies, and enforcement result. " +
"Be clear, concise, and educational.",
},
{
Role: "user",
Content: fmt.Sprintf("Please explain the following authorization decision:\n\n%s", explainContext),
},
}
reqBody := aiChatRequest{
Model: e.aiConfig.Model,
Messages: messages,
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("failed to marshal request: %w", err)
}
// Create HTTP request with context
reqCtx, cancel := context.WithTimeout(context.Background(), e.aiConfig.Timeout)
defer cancel()
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, e.aiConfig.Endpoint, bytes.NewBuffer(jsonData))
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+e.aiConfig.APIKey)
// Execute request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
// Parse response
var chatResp aiChatResponse
if err := json.Unmarshal(body, &chatResp); err != nil {
return "", fmt.Errorf("failed to parse response: %w", err)
}
// Check for API errors
if chatResp.Error != nil {
return "", fmt.Errorf("API error: %s", chatResp.Error.Message)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
}
// Extract explanation
if len(chatResp.Choices) == 0 {
return "", errors.New("no response from AI")
}
return chatResp.Choices[0].Message.Content, nil
}
================================================
FILE: ai_api_test.go
================================================
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// TestExplainWithoutConfig tests that Explain returns error when config is not set.
func TestExplainWithoutConfig(t *testing.T) {
e, err := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
if err != nil {
t.Fatal(err)
}
_, err = e.Explain("alice", "data1", "read")
if err == nil {
t.Error("Expected error when AI config is not set")
}
if !strings.Contains(err.Error(), "AI config not set") {
t.Errorf("Expected 'AI config not set' error, got: %v", err)
}
}
// TestExplainWithMockAPI tests Explain with a mock OpenAI-compatible API.
func TestExplainWithMockAPI(t *testing.T) {
// Create a mock server that simulates OpenAI API
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify request
if r.Method != http.MethodPost {
t.Errorf("Expected POST request, got %s", r.Method)
}
if r.Header.Get("Content-Type") != "application/json" {
t.Errorf("Expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
}
if !strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
t.Errorf("Expected Bearer token in Authorization header, got %s", r.Header.Get("Authorization"))
}
// Parse request to verify structure
var req aiChatRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Errorf("Failed to decode request: %v", err)
}
if req.Model != "gpt-3.5-turbo" {
t.Errorf("Expected model gpt-3.5-turbo, got %s", req.Model)
}
if len(req.Messages) != 2 {
t.Errorf("Expected 2 messages, got %d", len(req.Messages))
}
// Send mock response
resp := aiChatResponse{
Choices: []struct {
Message aiMessage `json:"message"`
}{
{
Message: aiMessage{
Role: "assistant",
Content: "The request was allowed because alice has read permission on data1 according to the policy rule.",
},
},
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}))
defer mockServer.Close()
// Create enforcer
e, err := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
if err != nil {
t.Fatal(err)
}
// Set AI config with mock server
e.SetAIConfig(AIConfig{
Endpoint: mockServer.URL,
APIKey: "test-api-key",
Model: "gpt-3.5-turbo",
Timeout: 5 * time.Second,
})
// Test explanation for allowed request
explanation, err := e.Explain("alice", "data1", "read")
if err != nil {
t.Fatalf("Failed to get explanation: %v", err)
}
if explanation == "" {
t.Error("Expected non-empty explanation")
}
if !strings.Contains(explanation, "allowed") {
t.Errorf("Expected explanation to mention 'allowed', got: %s", explanation)
}
}
// TestExplainDenied tests Explain for a denied request.
func TestExplainDenied(t *testing.T) {
// Create a mock server
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := aiChatResponse{
Choices: []struct {
Message aiMessage `json:"message"`
}{
{
Message: aiMessage{
Role: "assistant",
Content: "The request was denied because there is no policy rule that allows alice to write to data1.",
},
},
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}))
defer mockServer.Close()
// Create enforcer
e, err := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
if err != nil {
t.Fatal(err)
}
// Set AI config
e.SetAIConfig(AIConfig{
Endpoint: mockServer.URL,
APIKey: "test-api-key",
Model: "gpt-3.5-turbo",
Timeout: 5 * time.Second,
})
// Test explanation for denied request
explanation, err := e.Explain("alice", "data1", "write")
if err != nil {
t.Fatalf("Failed to get explanation: %v", err)
}
if explanation == "" {
t.Error("Expected non-empty explanation")
}
if !strings.Contains(explanation, "denied") {
t.Errorf("Expected explanation to mention 'denied', got: %s", explanation)
}
}
// TestExplainAPIError tests handling of API errors.
func TestExplainAPIError(t *testing.T) {
// Create a mock server that returns an error
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := aiChatResponse{
Error: &struct {
Message string `json:"message"`
}{
Message: "Invalid API key",
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(resp)
}))
defer mockServer.Close()
// Create enforcer
e, err := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
if err != nil {
t.Fatal(err)
}
// Set AI config
e.SetAIConfig(AIConfig{
Endpoint: mockServer.URL,
APIKey: "invalid-key",
Model: "gpt-3.5-turbo",
Timeout: 5 * time.Second,
})
// Test that API error is properly handled
_, err = e.Explain("alice", "data1", "read")
if err == nil {
t.Error("Expected error for API failure")
}
if !strings.Contains(err.Error(), "Invalid API key") {
t.Errorf("Expected API error message, got: %v", err)
}
}
// TestBuildExplainContext tests the context building function.
func TestBuildExplainContext(t *testing.T) {
e, err := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
if err != nil {
t.Fatal(err)
}
// Test with matched rules
rvals := []interface{}{"alice", "data1", "read"}
result := true
matchedRules := []string{"alice, data1, read"}
context := e.buildExplainContext(rvals, result, matchedRules)
// Verify context contains expected elements
if !strings.Contains(context, "alice") {
t.Error("Context should contain subject 'alice'")
}
if !strings.Contains(context, "data1") {
t.Error("Context should contain object 'data1'")
}
if !strings.Contains(context, "read") {
t.Error("Context should contain action 'read'")
}
if !strings.Contains(context, "true") {
t.Error("Context should contain result 'true'")
}
if !strings.Contains(context, "alice, data1, read") {
t.Error("Context should contain matched rule")
}
// Test with no matched rules
context2 := e.buildExplainContext(rvals, false, []string{})
if !strings.Contains(context2, "No policy rules matched") {
t.Error("Context should indicate no matched rules")
}
}
================================================
FILE: biba_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
func testEnforceBiba(t *testing.T, e *Enforcer, sub string, subLevel float64, obj string, objLevel float64, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, subLevel, obj, objLevel, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, %v, %s, %v, %s: %t, supposed to be %t", sub, subLevel, obj, objLevel, act, myRes, res)
}
}
func TestBibaModel(t *testing.T) {
e, _ := NewEnforcer("examples/biba_model.conf")
testEnforceBiba(t, e, "alice", 3, "data1", 1, "read", false)
testEnforceBiba(t, e, "bob", 2, "data2", 2, "read", true)
testEnforceBiba(t, e, "charlie", 1, "data1", 1, "read", true)
testEnforceBiba(t, e, "bob", 2, "data3", 3, "read", true)
testEnforceBiba(t, e, "charlie", 1, "data2", 2, "read", true)
testEnforceBiba(t, e, "alice", 3, "data3", 3, "write", true)
testEnforceBiba(t, e, "bob", 2, "data3", 3, "write", false)
testEnforceBiba(t, e, "charlie", 1, "data2", 2, "write", false)
testEnforceBiba(t, e, "alice", 3, "data1", 1, "write", true)
testEnforceBiba(t, e, "bob", 2, "data1", 1, "write", true)
}
================================================
FILE: blp_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
func testEnforceBLP(t *testing.T, e *Enforcer, sub string, subLevel float64, obj string, objLevel float64, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, subLevel, obj, objLevel, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, %v, %s, %v, %s: %t, supposed to be %t", sub, subLevel, obj, objLevel, act, myRes, res)
}
}
func TestBLPModel(t *testing.T) {
e, _ := NewEnforcer("examples/blp_model.conf")
// Read operations: subject level >= object level
testEnforceBLP(t, e, "alice", 3, "data1", 1, "read", true)
testEnforceBLP(t, e, "bob", 2, "data2", 2, "read", true)
testEnforceBLP(t, e, "charlie", 1, "data1", 1, "read", true)
// Read violations: subject level < object level
testEnforceBLP(t, e, "bob", 2, "data3", 3, "read", false)
testEnforceBLP(t, e, "charlie", 1, "data2", 2, "read", false)
// Write operations: subject level <= object level
testEnforceBLP(t, e, "alice", 3, "data3", 3, "write", true)
testEnforceBLP(t, e, "bob", 2, "data3", 3, "write", true)
testEnforceBLP(t, e, "charlie", 1, "data2", 2, "write", true)
// Write violations: subject level > object level
testEnforceBLP(t, e, "alice", 3, "data1", 1, "write", false)
testEnforceBLP(t, e, "bob", 2, "data1", 1, "write", false)
}
================================================
FILE: config/config.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"strconv"
"strings"
)
var (
// DEFAULT_SECTION specifies the name of a section if no name provided.
DEFAULT_SECTION = "default"
// DEFAULT_COMMENT defines what character(s) indicate a comment `#`.
DEFAULT_COMMENT = []byte{'#'}
// DEFAULT_COMMENT_SEM defines what alternate character(s) indicate a comment `;`.
DEFAULT_COMMENT_SEM = []byte{';'}
// DEFAULT_MULTI_LINE_SEPARATOR defines what character indicates a multi-line content.
DEFAULT_MULTI_LINE_SEPARATOR = []byte{'\\'}
)
// ConfigInterface defines the behavior of a Config implementation.
type ConfigInterface interface {
String(key string) string
Strings(key string) []string
Bool(key string) (bool, error)
Int(key string) (int, error)
Int64(key string) (int64, error)
Float64(key string) (float64, error)
Set(key string, value string) error
}
// Config represents an implementation of the ConfigInterface.
type Config struct {
// Section:key=value
data map[string]map[string]string
}
// NewConfig create an empty configuration representation from file.
func NewConfig(confName string) (ConfigInterface, error) {
c := &Config{
data: make(map[string]map[string]string),
}
err := c.parse(confName)
return c, err
}
// NewConfigFromText create an empty configuration representation from text.
func NewConfigFromText(text string) (ConfigInterface, error) {
c := &Config{
data: make(map[string]map[string]string),
}
err := c.parseBuffer(bufio.NewReader(strings.NewReader(text)))
return c, err
}
// AddConfig adds a new section->key:value to the configuration.
func (c *Config) AddConfig(section string, option string, value string) bool {
if section == "" {
section = DEFAULT_SECTION
}
if _, ok := c.data[section]; !ok {
c.data[section] = make(map[string]string)
}
_, ok := c.data[section][option]
c.data[section][option] = value
return !ok
}
func (c *Config) parse(fname string) (err error) {
f, err := os.Open(fname)
if err != nil {
return err
}
defer f.Close()
buf := bufio.NewReader(f)
return c.parseBuffer(buf)
}
func (c *Config) parseBuffer(buf *bufio.Reader) error {
var section string
var lineNum int
var buffer bytes.Buffer
var canWrite bool
for {
if canWrite {
if err := c.write(section, lineNum, &buffer); err != nil {
return err
} else {
canWrite = false
}
}
lineNum++
line, _, err := buf.ReadLine()
if err == io.EOF {
// force write when buffer is not flushed yet
if buffer.Len() > 0 {
if err = c.write(section, lineNum, &buffer); err != nil {
return err
}
}
break
} else if err != nil {
return err
}
line = bytes.TrimSpace(line)
switch {
case bytes.Equal(line, []byte{}), bytes.HasPrefix(line, DEFAULT_COMMENT_SEM),
bytes.HasPrefix(line, DEFAULT_COMMENT):
canWrite = true
continue
case bytes.HasPrefix(line, []byte{'['}) && bytes.HasSuffix(line, []byte{']'}):
// force write when buffer is not flushed yet
if buffer.Len() > 0 {
if err := c.write(section, lineNum, &buffer); err != nil {
return err
}
canWrite = false
}
section = string(line[1 : len(line)-1])
default:
var p []byte
if bytes.HasSuffix(line, DEFAULT_MULTI_LINE_SEPARATOR) {
p = bytes.TrimSpace(line[:len(line)-1])
p = append(p, " "...)
} else {
p = line
canWrite = true
}
end := len(p)
for i, value := range p {
if value == DEFAULT_COMMENT[0] || value == DEFAULT_COMMENT_SEM[0] {
end = i
break
}
}
if _, err := buffer.Write(p[:end]); err != nil {
return err
}
}
}
return nil
}
func (c *Config) write(section string, lineNum int, b *bytes.Buffer) error {
if b.Len() <= 0 {
return nil
}
optionVal := bytes.SplitN(b.Bytes(), []byte{'='}, 2)
if len(optionVal) != 2 {
return fmt.Errorf("parse the content error : line %d , %s = ? ", lineNum, optionVal[0])
}
option := bytes.TrimSpace(optionVal[0])
value := bytes.TrimSpace(optionVal[1])
c.AddConfig(section, string(option), string(value))
// flush buffer after adding
b.Reset()
return nil
}
// Bool lookups up the value using the provided key and converts the value to a bool.
func (c *Config) Bool(key string) (bool, error) {
return strconv.ParseBool(c.get(key))
}
// Int lookups up the value using the provided key and converts the value to a int.
func (c *Config) Int(key string) (int, error) {
return strconv.Atoi(c.get(key))
}
// Int64 lookups up the value using the provided key and converts the value to a int64.
func (c *Config) Int64(key string) (int64, error) {
return strconv.ParseInt(c.get(key), 10, 64)
}
// Float64 lookups up the value using the provided key and converts the value to a float64.
func (c *Config) Float64(key string) (float64, error) {
return strconv.ParseFloat(c.get(key), 64)
}
// String lookups up the value using the provided key and converts the value to a string.
func (c *Config) String(key string) string {
return c.get(key)
}
// Strings lookups up the value using the provided key and converts the value to an array of string
// by splitting the string by comma.
func (c *Config) Strings(key string) []string {
v := c.get(key)
if v == "" {
return nil
}
return strings.Split(v, ",")
}
// Set sets the value for the specific key in the Config.
func (c *Config) Set(key string, value string) error {
if len(key) == 0 {
return errors.New("key is empty")
}
var (
section string
option string
)
keys := strings.Split(strings.ToLower(key), "::")
if len(keys) >= 2 {
section = keys[0]
option = keys[1]
} else {
option = keys[0]
}
c.AddConfig(section, option, value)
return nil
}
// section.key or key.
func (c *Config) get(key string) string {
var (
section string
option string
)
keys := strings.Split(strings.ToLower(key), "::")
if len(keys) >= 2 {
section = keys[0]
option = keys[1]
} else {
section = DEFAULT_SECTION
option = keys[0]
}
if value, ok := c.data[section][option]; ok {
return value
}
return ""
}
================================================
FILE: config/config_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"testing"
)
func TestGet(t *testing.T) {
config, cerr := NewConfig("testdata/testini.ini")
if cerr != nil {
t.Errorf("Configuration file loading failed, err:%v", cerr.Error())
t.Fatalf("err: %v", cerr)
}
// default::key test
if v, err := config.Bool("debug"); err != nil || !v {
t.Errorf("Get failure: expected different value for debug (expected: [%#v] got: [%#v])", true, v)
t.Fatalf("err: %v", err)
}
if v := config.String("url"); v != "act.wiki" {
t.Errorf("Get failure: expected different value for url (expected: [%#v] got: [%#v])", "act.wiki", v)
}
// redis::key test
if v := config.Strings("redis::redis.key"); len(v) != 2 || v[0] != "push1" || v[1] != "push2" {
t.Errorf("Get failure: expected different value for redis::redis.key (expected: [%#v] got: [%#v])", "[]string{push1,push2}", v)
}
if v := config.String("mysql::mysql.dev.host"); v != "127.0.0.1" {
t.Errorf("Get failure: expected different value for mysql::mysql.dev.host (expected: [%#v] got: [%#v])", "127.0.0.1", v)
}
if v := config.String("mysql::mysql.master.host"); v != "10.0.0.1" {
t.Errorf("Get failure: expected different value for mysql::mysql.master.host (expected: [%#v] got: [%#v])", "10.0.0.1", v)
}
if v := config.String("mysql::mysql.master.user"); v != "root" {
t.Errorf("Get failure: expected different value for mysql::mysql.master.user (expected: [%#v] got: [%#v])", "root", v)
}
if v := config.String("mysql::mysql.master.pass"); v != "89dds)2$" {
t.Errorf("Get failure: expected different value for mysql::mysql.master.pass (expected: [%#v] got: [%#v])", "89dds)2$", v)
}
// math::key test
if v, err := config.Int64("math::math.i64"); err != nil || v != 64 {
t.Errorf("Get failure: expected different value for math::math.i64 (expected: [%#v] got: [%#v])", 64, v)
t.Fatalf("err: %v", err)
}
if v, err := config.Float64("math::math.f64"); err != nil || v != 64.1 {
t.Errorf("Get failure: expected different value for math::math.f64 (expected: [%#v] got: [%#v])", 64.1, v)
t.Fatalf("err: %v", err)
}
_ = config.Set("other::key1", "new test key")
if v := config.String("other::key1"); v != "new test key" {
t.Errorf("Get failure: expected different value for other::key1 (expected: [%#v] got: [%#v])", "new test key", v)
}
_ = config.Set("other::key1", "test key")
if v := config.String("multi1::name"); v != "r.sub==p.sub && r.obj==p.obj" {
t.Errorf("Get failure: expected different value for multi1::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
}
if v := config.String("multi2::name"); v != "r.sub==p.sub && r.obj==p.obj" {
t.Errorf("Get failure: expected different value for multi2::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
}
if v := config.String("multi3::name"); v != "r.sub==p.sub && r.obj==p.obj" {
t.Errorf("Get failure: expected different value for multi3::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
}
if v := config.String("multi4::name"); v != "" {
t.Errorf("Get failure: expected different value for multi4::name (expected: [%#v] got: [%#v])", "", v)
}
if v := config.String("multi5::name"); v != "r.sub==p.sub && r.obj==p.obj" {
t.Errorf("Get failure: expected different value for multi5::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
}
}
================================================
FILE: config/testdata/testini.ini
================================================
# test config
debug = true
url = act.wiki
; redis config
[redis]
redis.key = push1,push2
; mysql config
[mysql]
mysql.dev.host = 127.0.0.1
mysql.dev.user = root
mysql.dev.pass = 123456
mysql.dev.db = test
mysql.master.host = 10.0.0.1 # host # 10.0.0.1
mysql.master.user = root ; user name
mysql.master.pass = 89dds)2$#d
mysql.master.db = act
; math config
[math]
math.i64 = 64
math.f64 = 64.1
# multi-line test
[multi1]
name = r.sub==p.sub \
&& r.obj==p.obj\
\
[multi2]
name = r.sub==p.sub \
&& r.obj==p.obj
[multi3]
name = r.sub==p.sub \
&& r.obj==p.obj
[multi4]
name = \
\
\
[multi5]
name = r.sub==p.sub \
&& r.obj==p.obj\
\
================================================
FILE: constant/constants.go
================================================
// Copyright 2022 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package constant
const (
ActionIndex = "act"
DomainIndex = "dom"
SubjectIndex = "sub"
ObjectIndex = "obj"
PriorityIndex = "priority"
)
const (
AllowOverrideEffect = "some(where (p_eft == allow))"
DenyOverrideEffect = "!some(where (p_eft == deny))"
AllowAndDenyEffect = "some(where (p_eft == allow)) && !some(where (p_eft == deny))"
PriorityEffect = "priority(p_eft) || deny"
SubjectPriorityEffect = "subjectPriority(p_eft) || deny"
)
================================================
FILE: constraint_test.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"strings"
"testing"
"github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/model"
)
func TestConstraintSOD(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = sod("role1", "role2")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Add a user to role1 should succeed
_, err = e.AddRoleForUser("alice", "role1")
if err != nil {
t.Fatalf("Failed to add role1 to alice: %v", err)
}
// Add a different user to role2 should succeed
_, err = e.AddRoleForUser("bob", "role2")
if err != nil {
t.Fatalf("Failed to add role2 to bob: %v", err)
}
// Try to add role2 to alice should fail (SOD violation)
_, err = e.AddRoleForUser("alice", "role2")
if err == nil {
t.Fatal("Expected constraint violation error, got nil")
}
if !strings.Contains(err.Error(), "constraint violation") {
t.Fatalf("Expected constraint violation error, got: %v", err)
}
}
func TestConstraintSODMax(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = sodMax(["role1", "role2", "role3"], 1)
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Add user to one role should succeed
_, err = e.AddRoleForUser("alice", "role1")
if err != nil {
t.Fatalf("Failed to add role1 to alice: %v", err)
}
// Try to add user to another role from the set should fail
_, err = e.AddRoleForUser("alice", "role2")
if err == nil {
t.Fatal("Expected constraint violation error, got nil")
}
if !strings.Contains(err.Error(), "constraint violation") {
t.Fatalf("Expected constraint violation error, got: %v", err)
}
}
func TestConstraintRoleMax(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = roleMax("admin", 2)
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Add first user to admin role should succeed
_, err = e.AddRoleForUser("alice", "admin")
if err != nil {
t.Fatalf("Failed to add admin to alice: %v", err)
}
// Add second user to admin role should succeed
_, err = e.AddRoleForUser("bob", "admin")
if err != nil {
t.Fatalf("Failed to add admin to bob: %v", err)
}
// Try to add third user to admin role should fail (exceeds max)
_, err = e.AddRoleForUser("charlie", "admin")
if err == nil {
t.Fatal("Expected constraint violation error, got nil")
}
if !strings.Contains(err.Error(), "constraint violation") {
t.Fatalf("Expected constraint violation error, got: %v", err)
}
}
func TestConstraintRolePre(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = rolePre("db_admin", "security_trained")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Try to add db_admin without prerequisite should fail
_, err = e.AddRoleForUser("alice", "db_admin")
if err == nil {
t.Fatal("Expected constraint violation error, got nil")
}
if !strings.Contains(err.Error(), "constraint violation") {
t.Fatalf("Expected constraint violation error, got: %v", err)
}
// Add prerequisite role first
_, err = e.AddRoleForUser("alice", "security_trained")
if err != nil {
t.Fatalf("Failed to add security_trained to alice: %v", err)
}
// Now adding db_admin should succeed
_, err = e.AddRoleForUser("alice", "db_admin")
if err != nil {
t.Fatalf("Failed to add db_admin to alice: %v", err)
}
}
func TestConstraintWithoutRBAC(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[constraint_definition]
c = sod("role1", "role2")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
`
_, err := model.NewModelFromString(modelText)
if err == nil {
t.Fatal("Expected error for constraints without RBAC, got nil")
}
if err != errors.ErrConstraintRequiresRBAC {
t.Fatalf("Expected ErrConstraintRequiresRBAC, got: %v", err)
}
}
func TestConstraintParsingError(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = invalidFunction("role1", "role2")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
_, err := model.NewModelFromString(modelText)
if err == nil {
t.Fatal("Expected parsing error for invalid constraint, got nil")
}
if !strings.Contains(err.Error(), "constraint parsing error") {
t.Fatalf("Expected constraint parsing error, got: %v", err)
}
}
func TestConstraintRollback(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = sod("role1", "role2")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Add alice to role1
_, err = e.AddRoleForUser("alice", "role1")
if err != nil {
t.Fatalf("Failed to add role1 to alice: %v", err)
}
// Try to add alice to role2 (should fail with constraint violation)
_, err = e.AddRoleForUser("alice", "role2")
if err == nil {
t.Fatal("Expected constraint violation error, got nil")
}
if !strings.Contains(err.Error(), "constraint violation") {
t.Fatalf("Expected constraint violation error, got: %v", err)
}
}
================================================
FILE: detector/default_detector.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package detector
import (
"fmt"
"strings"
"github.com/casbin/casbin/v3/rbac"
)
// rangeableRM is an interface for role managers that support iterating over all role links.
// This is used to build the adjacency graph for cycle detection.
type rangeableRM interface {
Range(func(name1, name2 string, domain ...string) bool)
}
// DefaultDetector is the default implementation of the Detector interface.
// It uses depth-first search (DFS) to detect cycles in role inheritance.
type DefaultDetector struct{}
// NewDefaultDetector creates a new instance of DefaultDetector.
func NewDefaultDetector() *DefaultDetector {
return &DefaultDetector{}
}
// Check checks whether the current status of the passed-in RoleManager contains logical errors (e.g., cycles in role inheritance).
// It uses DFS to traverse the role graph and detect cycles.
// Returns nil if no cycle is found, otherwise returns an error with a description of the cycle.
func (d *DefaultDetector) Check(rm rbac.RoleManager) error {
// Defensive nil check to prevent runtime panics
if rm == nil {
return fmt.Errorf("role manager cannot be nil")
}
// Build the adjacency graph by exploring all roles
graph, err := d.buildGraph(rm)
if err != nil {
return err
}
// Run DFS to detect cycles
visited := make(map[string]bool)
recursionStack := make(map[string]bool)
for role := range graph {
if !visited[role] {
if cycle := d.detectCycle(role, graph, visited, recursionStack, []string{}); cycle != nil {
return fmt.Errorf("cycle detected: %s", strings.Join(cycle, " -> "))
}
}
}
return nil
}
// buildGraph builds an adjacency list representation of the role inheritance graph.
// It uses the Range method (via type assertion) to iterate through all role links.
func (d *DefaultDetector) buildGraph(rm rbac.RoleManager) (graph map[string][]string, err error) {
graph = make(map[string][]string)
// Recover from any panics during Range iteration (e.g., nil pointer dereferences)
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("RoleManager is not properly initialized: %v", r)
}
}()
// Try to cast to a RoleManager implementation that supports Range
// This works with RoleManagerImpl and similar implementations
rrm, ok := rm.(rangeableRM)
if !ok {
// Return an error if the RoleManager doesn't support Range iteration
return nil, fmt.Errorf("RoleManager does not support Range iteration, cannot detect cycles")
}
// Use Range method to build the graph directly
rrm.Range(func(name1, name2 string, domain ...string) bool {
// Initialize empty slice for name1 if it doesn't exist
if graph[name1] == nil {
graph[name1] = []string{}
}
// Add the link: name1 -> name2
graph[name1] = append(graph[name1], name2)
// Ensure name2 exists in graph even if it has no outgoing edges
if graph[name2] == nil {
graph[name2] = []string{}
}
return true
})
return graph, nil
}
// detectCycle performs DFS to detect cycles in the role graph.
// Returns a slice representing the cycle path if found, nil otherwise.
func (d *DefaultDetector) detectCycle(
role string,
graph map[string][]string,
visited map[string]bool,
recursionStack map[string]bool,
path []string,
) []string {
// Mark the current role as visited and add to recursion stack
visited[role] = true
recursionStack[role] = true
path = append(path, role)
// Visit all neighbors (parent roles)
for _, neighbor := range graph[role] {
if !visited[neighbor] {
// Recursively visit unvisited neighbor
if cycle := d.detectCycle(neighbor, graph, visited, recursionStack, path); cycle != nil {
return cycle
}
} else if recursionStack[neighbor] {
// Back edge found - cycle detected
// Find where the cycle starts in the path
cycleStart := -1
for i, p := range path {
if p == neighbor {
cycleStart = i
break
}
}
if cycleStart >= 0 {
// Build the cycle path
cyclePath := append([]string{}, path[cycleStart:]...)
cyclePath = append(cyclePath, neighbor)
return cyclePath
}
}
}
// Remove from recursion stack before returning
recursionStack[role] = false
return nil
}
================================================
FILE: detector/default_detector_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package detector
import (
"fmt"
"strings"
"testing"
defaultrolemanager "github.com/casbin/casbin/v3/rbac/default-role-manager"
)
func TestDefaultDetector_NilRoleManager(t *testing.T) {
detector := NewDefaultDetector()
err := detector.Check(nil)
if err == nil {
t.Error("Expected error for nil role manager, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "role manager cannot be nil") {
t.Errorf("Expected error message to contain 'role manager cannot be nil', got: %s", errMsg)
}
}
}
func TestDefaultDetector_NoCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("bob", "user")
_ = rm.AddLink("admin", "superuser")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle, but got error: %v", err)
}
}
func TestDefaultDetector_SimpleCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
_ = rm.AddLink("A", "B")
_ = rm.AddLink("B", "A")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
// Should contain both A and B in the cycle
if !strings.Contains(errMsg, "A") || !strings.Contains(errMsg, "B") {
t.Errorf("Expected error message to contain both A and B, got: %s", errMsg)
}
}
}
func TestDefaultDetector_ComplexCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
_ = rm.AddLink("A", "B")
_ = rm.AddLink("B", "C")
_ = rm.AddLink("C", "A")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
// Should contain A, B, and C in the cycle
if !strings.Contains(errMsg, "A") || !strings.Contains(errMsg, "B") || !strings.Contains(errMsg, "C") {
t.Errorf("Expected error message to contain A, B, and C, got: %s", errMsg)
}
}
}
func TestDefaultDetector_SelfLoop(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
_ = rm.AddLink("A", "A")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error for self-loop, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
func TestDefaultDetector_MultipleCycles(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// First cycle: A -> B -> A
_ = rm.AddLink("A", "B")
_ = rm.AddLink("B", "A")
// Second cycle: C -> D -> C
_ = rm.AddLink("C", "D")
_ = rm.AddLink("D", "C")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
func TestDefaultDetector_DisconnectedComponents(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// Component 1: alice -> admin -> superuser
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("admin", "superuser")
// Component 2: bob -> user
_ = rm.AddLink("bob", "user")
// Component 3: carol -> moderator
_ = rm.AddLink("carol", "moderator")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle in disconnected components, but got error: %v", err)
}
}
func TestDefaultDetector_ComplexGraphWithCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// Build a complex graph with one cycle
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u2", "g1")
_ = rm.AddLink("g1", "g2")
_ = rm.AddLink("g2", "g3")
_ = rm.AddLink("g3", "g1") // Creates cycle: g1 -> g2 -> g3 -> g1
_ = rm.AddLink("u3", "g4")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
func TestDefaultDetector_LongCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(20)
// Create a long cycle: A -> B -> C -> D -> E -> A
_ = rm.AddLink("A", "B")
_ = rm.AddLink("B", "C")
_ = rm.AddLink("C", "D")
_ = rm.AddLink("D", "E")
_ = rm.AddLink("E", "A")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
func TestDefaultDetector_EmptyRoleManager(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no error for empty role manager, but got: %v", err)
}
}
func TestDefaultDetector_LargeGraphNoCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(100)
// Build a large graph with no cycles: a tree structure
// Create 100 levels: u0 -> u1 -> u2 -> ... -> u99
for i := 0; i < 99; i++ {
user := fmt.Sprintf("u%d", i)
role := fmt.Sprintf("u%d", i+1)
_ = rm.AddLink(user, role)
}
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle in large graph, but got error: %v", err)
}
}
func TestDefaultDetector_LargeGraphWithCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(100)
// Build a large graph with a cycle at the end
// Create a chain: u0 -> u1 -> u2 -> ... -> u99 -> u0
for i := 0; i < 99; i++ {
user := fmt.Sprintf("u%d", i)
role := fmt.Sprintf("u%d", i+1)
_ = rm.AddLink(user, role)
}
// Add the cycle
_ = rm.AddLink("u99", "u0")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error in large graph, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
// Performance test with 10,000 roles.
func TestDefaultDetector_PerformanceLargeGraph(t *testing.T) {
if testing.Short() {
t.Skip("Skipping performance test in short mode")
}
// Use a higher maxHierarchyLevel to support deep hierarchies
rm := defaultrolemanager.NewRoleManagerImpl(10000)
// Build a large tree structure with 10,000 roles
// Each role has up to 3 children
numRoles := 10000
for i := 0; i < numRoles; i++ {
role := fmt.Sprintf("r%d", i)
// Add links to create a tree structure
child1 := (i * 3) + 1
child2 := (i * 3) + 2
child3 := (i * 3) + 3
if child1 < numRoles {
_ = rm.AddLink(fmt.Sprintf("r%d", child1), role)
}
if child2 < numRoles {
_ = rm.AddLink(fmt.Sprintf("r%d", child2), role)
}
if child3 < numRoles {
_ = rm.AddLink(fmt.Sprintf("r%d", child3), role)
}
}
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle in large performance test, but got error: %v", err)
}
}
func TestDefaultDetector_MultipleInheritance(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// User inherits from multiple roles
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("alice", "moderator")
_ = rm.AddLink("admin", "superuser")
_ = rm.AddLink("moderator", "user")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle with multiple inheritance, but got error: %v", err)
}
}
func TestDefaultDetector_DiamondPattern(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// Diamond pattern: alice -> admin, alice -> moderator, admin -> superuser, moderator -> superuser
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("alice", "moderator")
_ = rm.AddLink("admin", "superuser")
_ = rm.AddLink("moderator", "superuser")
detector := NewDefaultDetector()
err := detector.Check(rm)
if err != nil {
t.Errorf("Expected no cycle in diamond pattern, but got error: %v", err)
}
}
func TestDefaultDetector_DiamondPatternWithCycle(t *testing.T) {
rm := defaultrolemanager.NewRoleManagerImpl(10)
// Diamond pattern with cycle: alice -> admin, alice -> moderator, admin -> superuser, moderator -> superuser, superuser -> alice
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("alice", "moderator")
_ = rm.AddLink("admin", "superuser")
_ = rm.AddLink("moderator", "superuser")
_ = rm.AddLink("superuser", "alice") // Creates cycle
detector := NewDefaultDetector()
err := detector.Check(rm)
if err == nil {
t.Error("Expected cycle detection error in diamond pattern with cycle, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'Cycle detected', got: %s", errMsg)
}
}
}
================================================
FILE: detector/detector.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package detector
import "github.com/casbin/casbin/v3/rbac"
// Detector defines the interface of a policy consistency checker, currently used to detect RBAC inheritance cycles.
type Detector interface {
// Check checks whether the current status of the passed-in RoleManager contains logical errors (e.g., cycles in role inheritance).
// param: rm RoleManager instance
// return: If an error is found, return a descriptive error; otherwise return nil.
Check(rm rbac.RoleManager) error
}
================================================
FILE: effector/default_effector.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package effector
import (
"errors"
"github.com/casbin/casbin/v3/constant"
)
// DefaultEffector is default effector for Casbin.
type DefaultEffector struct {
}
// NewDefaultEffector is the constructor for DefaultEffector.
func NewDefaultEffector() *DefaultEffector {
e := DefaultEffector{}
return &e
}
// MergeEffects merges all matching results collected by the enforcer into a single decision.
func (e *DefaultEffector) MergeEffects(expr string, effects []Effect, matches []float64, policyIndex int, policyLength int) (Effect, int, error) {
result := Indeterminate
explainIndex := -1
switch expr {
case constant.AllowOverrideEffect:
if matches[policyIndex] == 0 {
break
}
// only check the current policyIndex
if effects[policyIndex] == Allow {
result = Allow
explainIndex = policyIndex
break
}
case constant.DenyOverrideEffect:
// only check the current policyIndex
if matches[policyIndex] != 0 && effects[policyIndex] == Deny {
result = Deny
explainIndex = policyIndex
break
}
// if no deny rules are matched at last, then allow
if policyIndex == policyLength-1 {
result = Allow
}
case constant.AllowAndDenyEffect:
// short-circuit if matched deny rule
if matches[policyIndex] != 0 && effects[policyIndex] == Deny {
result = Deny
// set hit rule to the (first) matched deny rule
explainIndex = policyIndex
break
}
// short-circuit some effects in the middle
if policyIndex < policyLength-1 {
// choose not to short-circuit
return result, explainIndex, nil
}
// merge all effects at last
for i, eft := range effects {
if matches[i] == 0 {
continue
}
if eft == Allow {
result = Allow
// set hit rule to first matched allow rule
explainIndex = i
break
}
}
case constant.PriorityEffect, constant.SubjectPriorityEffect:
// reverse merge, short-circuit may be earlier
for i := len(effects) - 1; i >= 0; i-- {
if matches[i] == 0 {
continue
}
if effects[i] != Indeterminate {
if effects[i] == Allow {
result = Allow
} else {
result = Deny
}
explainIndex = i
break
}
}
default:
return Deny, -1, errors.New("unsupported effect")
}
return result, explainIndex, nil
}
================================================
FILE: effector/effector.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package effector //nolint:cyclop // TODO
// Effect is the result for a policy rule.
type Effect int
// Values for policy effect.
const (
Allow Effect = iota
Indeterminate
Deny
)
// Effector is the interface for Casbin effectors.
type Effector interface {
// MergeEffects merges all matching results collected by the enforcer into a single decision.
MergeEffects(expr string, effects []Effect, matches []float64, policyIndex int, policyLength int) (Effect, int, error)
}
================================================
FILE: enforcer.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"errors"
"fmt"
"runtime/debug"
"strings"
"sync"
"github.com/casbin/casbin/v3/detector"
"github.com/casbin/casbin/v3/effector"
"github.com/casbin/casbin/v3/log"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
"github.com/casbin/casbin/v3/rbac"
defaultrolemanager "github.com/casbin/casbin/v3/rbac/default-role-manager"
"github.com/casbin/casbin/v3/util"
"github.com/casbin/govaluate"
)
// Enforcer is the main interface for authorization enforcement and policy management.
type Enforcer struct {
modelPath string
model model.Model
fm model.FunctionMap
eft effector.Effector
adapter persist.Adapter
watcher persist.Watcher
dispatcher persist.Dispatcher
rmMap map[string]rbac.RoleManager
condRmMap map[string]rbac.ConditionalRoleManager
matcherMap sync.Map
logger log.Logger
detectors []detector.Detector
enabled bool
autoSave bool
autoBuildRoleLinks bool
autoNotifyWatcher bool
autoNotifyDispatcher bool
acceptJsonRequest bool
aiConfig AIConfig
}
// EnforceContext is used as the first element of the parameter "rvals" in method "enforce".
type EnforceContext struct {
RType string
PType string
EType string
MType string
}
func (e EnforceContext) GetCacheKey() string {
return "EnforceContext{" + e.RType + "-" + e.PType + "-" + e.EType + "-" + e.MType + "}"
}
// NewEnforcer creates an enforcer via file or DB.
//
// File:
//
// e := casbin.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv")
//
// MySQL DB:
//
// a := mysqladapter.NewDBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
// e := casbin.NewEnforcer("path/to/basic_model.conf", a)
func NewEnforcer(params ...interface{}) (*Enforcer, error) {
e := &Enforcer{}
parsedParamLen := 0
paramLen := len(params)
switch paramLen - parsedParamLen {
case 2:
switch p0 := params[0].(type) {
case string:
switch p1 := params[1].(type) {
case string:
err := e.InitWithFile(p0, p1)
if err != nil {
return nil, err
}
default:
err := e.InitWithAdapter(p0, p1.(persist.Adapter))
if err != nil {
return nil, err
}
}
default:
switch params[1].(type) {
case string:
return nil, errors.New("invalid parameters for enforcer")
default:
err := e.InitWithModelAndAdapter(p0.(model.Model), params[1].(persist.Adapter))
if err != nil {
return nil, err
}
}
}
case 1:
switch p0 := params[0].(type) {
case string:
err := e.InitWithFile(p0, "")
if err != nil {
return nil, err
}
default:
err := e.InitWithModelAndAdapter(p0.(model.Model), nil)
if err != nil {
return nil, err
}
}
case 0:
return e, nil
default:
return nil, errors.New("invalid parameters for enforcer")
}
return e, nil
}
// InitWithFile initializes an enforcer with a model file and a policy file.
func (e *Enforcer) InitWithFile(modelPath string, policyPath string) error {
a := fileadapter.NewAdapter(policyPath)
return e.InitWithAdapter(modelPath, a)
}
// InitWithAdapter initializes an enforcer with a database adapter.
func (e *Enforcer) InitWithAdapter(modelPath string, adapter persist.Adapter) error {
m, err := model.NewModelFromFile(modelPath)
if err != nil {
return err
}
err = e.InitWithModelAndAdapter(m, adapter)
if err != nil {
return err
}
e.modelPath = modelPath
return nil
}
// InitWithModelAndAdapter initializes an enforcer with a model and a database adapter.
func (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error {
e.adapter = adapter
e.model = m
e.model.PrintModel()
e.fm = model.LoadFunctionMap()
e.initialize()
// Do not initialize the full policy when using a filtered adapter
fa, ok := e.adapter.(persist.FilteredAdapter)
if e.adapter != nil && (!ok || ok && !fa.IsFiltered()) {
err := e.LoadPolicy()
if err != nil {
return err
}
}
return nil
}
func (e *Enforcer) initialize() {
e.rmMap = map[string]rbac.RoleManager{}
e.condRmMap = map[string]rbac.ConditionalRoleManager{}
e.eft = effector.NewDefaultEffector()
e.watcher = nil
e.matcherMap = sync.Map{}
e.enabled = true
e.autoSave = true
e.autoBuildRoleLinks = true
e.autoNotifyWatcher = true
e.autoNotifyDispatcher = true
e.initRmMap()
// Initialize detectors with default detector if not already set
if e.detectors == nil {
e.detectors = []detector.Detector{detector.NewDefaultDetector()}
}
}
// LoadModel reloads the model from the model CONF file.
// Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy().
func (e *Enforcer) LoadModel() error {
var err error
e.model, err = model.NewModelFromFile(e.modelPath)
if err != nil {
return err
}
e.model.PrintModel()
e.fm = model.LoadFunctionMap()
e.initialize()
return nil
}
// GetModel gets the current model.
func (e *Enforcer) GetModel() model.Model {
return e.model
}
// SetModel sets the current model.
func (e *Enforcer) SetModel(m model.Model) {
e.model = m
e.fm = model.LoadFunctionMap()
e.initialize()
}
// GetAdapter gets the current adapter.
func (e *Enforcer) GetAdapter() persist.Adapter {
return e.adapter
}
// SetAdapter sets the current adapter.
func (e *Enforcer) SetAdapter(adapter persist.Adapter) {
e.adapter = adapter
}
// SetWatcher sets the current watcher.
func (e *Enforcer) SetWatcher(watcher persist.Watcher) error {
e.watcher = watcher
if _, ok := e.watcher.(persist.WatcherEx); ok {
// The callback of WatcherEx has no generic implementation.
return nil
} else {
// In case the Watcher wants to use a customized callback function, call `SetUpdateCallback` after `SetWatcher`.
return watcher.SetUpdateCallback(func(string) { _ = e.LoadPolicy() })
}
}
// GetRoleManager gets the current role manager.
func (e *Enforcer) GetRoleManager() rbac.RoleManager {
if e.rmMap != nil && e.rmMap["g"] != nil {
return e.rmMap["g"]
} else if e.condRmMap != nil && e.condRmMap["g"] != nil {
return e.condRmMap["g"]
} else {
return nil
}
}
// GetNamedRoleManager gets the role manager for the named policy.
func (e *Enforcer) GetNamedRoleManager(ptype string) rbac.RoleManager {
if e.rmMap != nil && e.rmMap[ptype] != nil {
return e.rmMap[ptype]
} else if e.condRmMap != nil && e.condRmMap[ptype] != nil {
return e.condRmMap[ptype]
} else {
return nil
}
}
// SetRoleManager sets the current role manager.
func (e *Enforcer) SetRoleManager(rm rbac.RoleManager) {
e.invalidateMatcherMap()
e.rmMap["g"] = rm
}
// SetNamedRoleManager sets the role manager for the named policy.
func (e *Enforcer) SetNamedRoleManager(ptype string, rm rbac.RoleManager) {
e.invalidateMatcherMap()
e.rmMap[ptype] = rm
}
// SetEffector sets the current effector.
func (e *Enforcer) SetEffector(eft effector.Effector) {
e.eft = eft
}
// SetLogger sets the logger for the enforcer.
func (e *Enforcer) SetLogger(logger log.Logger) {
e.logger = logger
}
// SetDetector sets a single detector for the enforcer.
func (e *Enforcer) SetDetector(d detector.Detector) {
e.detectors = []detector.Detector{d}
}
// SetDetectors sets multiple detectors for the enforcer.
func (e *Enforcer) SetDetectors(detectors []detector.Detector) {
e.detectors = detectors
}
// RunDetections runs all detectors on all role managers.
// Returns the first error encountered, or nil if all checks pass.
// Silently skips role managers that don't support the required iteration methods.
func (e *Enforcer) RunDetections() error {
if e.detectors == nil || len(e.detectors) == 0 {
return nil
}
// Run detectors on all role managers
for _, rm := range e.rmMap {
for _, d := range e.detectors {
err := d.Check(rm)
// Skip if the role manager doesn't support the required iteration or is not initialized
if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") ||
strings.Contains(err.Error(), "not properly initialized")) {
continue
}
if err != nil {
return err
}
}
}
// Run detectors on all conditional role managers
for _, crm := range e.condRmMap {
for _, d := range e.detectors {
err := d.Check(crm)
// Skip if the role manager doesn't support the required iteration or is not initialized
if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") ||
strings.Contains(err.Error(), "not properly initialized")) {
continue
}
if err != nil {
return err
}
}
}
return nil
}
// ClearPolicy clears all policy.
func (e *Enforcer) ClearPolicy() {
e.invalidateMatcherMap()
if e.dispatcher != nil && e.autoNotifyDispatcher {
_ = e.dispatcher.ClearPolicy()
return
}
e.model.ClearPolicy()
}
// LoadPolicy reloads the policy from file/database.
func (e *Enforcer) LoadPolicy() error {
logEntry := e.onLogBeforeEventInLoadPolicy()
newModel, err := e.loadPolicyFromAdapter(e.model)
if err != nil {
e.onLogAfterEventWithError(logEntry, err)
return err
}
err = e.applyModifiedModel(newModel)
if err != nil {
e.onLogAfterEventWithError(logEntry, err)
return err
}
e.onLogAfterEventInLoadPolicy(logEntry, newModel)
// Run detectors after all policy rules are loaded
err = e.RunDetections()
if err != nil {
return err
}
return nil
}
func (e *Enforcer) loadPolicyFromAdapter(baseModel model.Model) (model.Model, error) {
newModel := baseModel.Copy()
newModel.ClearPolicy()
if err := e.adapter.LoadPolicy(newModel); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
return nil, err
}
if err := newModel.SortPoliciesBySubjectHierarchy(); err != nil {
return nil, err
}
if err := newModel.SortPoliciesByPriority(); err != nil {
return nil, err
}
return newModel, nil
}
func (e *Enforcer) applyModifiedModel(newModel model.Model) error {
var err error
needToRebuild := false
defer func() {
if err != nil {
if e.autoBuildRoleLinks && needToRebuild {
_ = e.BuildRoleLinks()
}
}
}()
if e.autoBuildRoleLinks {
needToRebuild = true
if err := e.rebuildRoleLinks(newModel); err != nil {
return err
}
if err := e.rebuildConditionalRoleLinks(newModel); err != nil {
return err
}
}
e.model = newModel
e.invalidateMatcherMap()
return nil
}
func (e *Enforcer) rebuildRoleLinks(newModel model.Model) error {
if len(e.rmMap) != 0 {
for _, rm := range e.rmMap {
err := rm.Clear()
if err != nil {
return err
}
}
err := newModel.BuildRoleLinks(e.rmMap)
if err != nil {
return err
}
}
return nil
}
func (e *Enforcer) rebuildConditionalRoleLinks(newModel model.Model) error {
if len(e.condRmMap) != 0 {
for _, crm := range e.condRmMap {
err := crm.Clear()
if err != nil {
return err
}
}
err := newModel.BuildConditionalRoleLinks(e.condRmMap)
if err != nil {
return err
}
}
return nil
}
func (e *Enforcer) loadFilteredPolicy(filter interface{}) error {
e.invalidateMatcherMap()
var filteredAdapter persist.FilteredAdapter
// Attempt to cast the Adapter as a FilteredAdapter
switch adapter := e.adapter.(type) {
case persist.FilteredAdapter:
filteredAdapter = adapter
default:
return errors.New("filtered policies are not supported by this adapter")
}
if err := filteredAdapter.LoadFilteredPolicy(e.model, filter); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
return err
}
if err := e.model.SortPoliciesBySubjectHierarchy(); err != nil {
return err
}
if err := e.model.SortPoliciesByPriority(); err != nil {
return err
}
e.initRmMap()
e.model.PrintPolicy()
if e.autoBuildRoleLinks {
err := e.BuildRoleLinks()
if err != nil {
return err
}
}
return nil
}
// LoadFilteredPolicy reloads a filtered policy from file/database.
func (e *Enforcer) LoadFilteredPolicy(filter interface{}) error {
e.model.ClearPolicy()
return e.loadFilteredPolicy(filter)
}
// LoadIncrementalFilteredPolicy append a filtered policy from file/database.
func (e *Enforcer) LoadIncrementalFilteredPolicy(filter interface{}) error {
return e.loadFilteredPolicy(filter)
}
// IsFiltered returns true if the loaded policy has been filtered.
func (e *Enforcer) IsFiltered() bool {
filteredAdapter, ok := e.adapter.(persist.FilteredAdapter)
if !ok {
return false
}
return filteredAdapter.IsFiltered()
}
// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database.
func (e *Enforcer) SavePolicy() error {
logEntry := e.onLogBeforeEventInSavePolicy()
if e.IsFiltered() {
err := errors.New("cannot save a filtered policy")
e.onLogAfterEventWithError(logEntry, err)
return err
}
if err := e.adapter.SavePolicy(e.model); err != nil {
e.onLogAfterEventWithError(logEntry, err)
return err
}
e.onLogAfterEventInSavePolicy(logEntry)
if e.watcher != nil {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForSavePolicy(e.model)
} else {
err = e.watcher.Update()
}
return err
}
return nil
}
// getDomainTokens extracts domain token names from request and policy definitions.
// Returns empty strings if tokens cannot be found.
func (e *Enforcer) getDomainTokens() (rDomainToken, pDomainToken string) {
if rAssertion, ok := e.model["r"]["r"]; ok && len(rAssertion.Tokens) > 1 {
rDomainToken = rAssertion.Tokens[1]
}
if pAssertion, ok := e.model["p"]["p"]; ok && len(pAssertion.Tokens) > 1 {
pDomainToken = pAssertion.Tokens[1]
}
return rDomainToken, pDomainToken
}
// registerDomainMatchingFunc registers domain matching function if the matcher uses keyMatch for domains.
func (e *Enforcer) registerDomainMatchingFunc(ptype string) {
// Dynamically detect the domain token name from the model definition.
// In RBAC with domains, the domain is typically the second parameter (index 1)
// in both request and policy definitions (e.g., r = sub, dom, obj, act).
// We extract the actual token names to support arbitrary domain parameter names.
rDomainToken, pDomainToken := e.getDomainTokens()
if rDomainToken == "" || pDomainToken == "" {
return
}
matchFun := fmt.Sprintf("keyMatch(%s, %s)", rDomainToken, pDomainToken)
if strings.Contains(e.model["m"]["m"].Value, matchFun) {
e.AddNamedDomainMatchingFunc(ptype, "g", util.KeyMatch)
}
}
func (e *Enforcer) initRmMap() {
for ptype, assertion := range e.model["g"] {
if rm, ok := e.rmMap[ptype]; ok {
_ = rm.Clear()
continue
}
if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) == 0 {
assertion.RM = defaultrolemanager.NewRoleManagerImpl(10)
e.rmMap[ptype] = assertion.RM
}
if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) != 0 {
assertion.CondRM = defaultrolemanager.NewConditionalRoleManager(10)
e.condRmMap[ptype] = assertion.CondRM
}
if len(assertion.Tokens) > 2 {
if len(assertion.ParamsTokens) == 0 {
assertion.RM = defaultrolemanager.NewRoleManager(10)
e.rmMap[ptype] = assertion.RM
} else {
assertion.CondRM = defaultrolemanager.NewConditionalDomainManager(10)
e.condRmMap[ptype] = assertion.CondRM
}
e.registerDomainMatchingFunc(ptype)
}
}
}
// EnableEnforce changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function.
func (e *Enforcer) EnableEnforce(enable bool) {
e.enabled = enable
}
// EnableAutoNotifyWatcher controls whether to save a policy rule automatically notify the Watcher when it is added or removed.
func (e *Enforcer) EnableAutoNotifyWatcher(enable bool) {
e.autoNotifyWatcher = enable
}
// EnableAutoNotifyDispatcher controls whether to save a policy rule automatically notify the Dispatcher when it is added or removed.
func (e *Enforcer) EnableAutoNotifyDispatcher(enable bool) {
e.autoNotifyDispatcher = enable
}
// EnableAutoSave controls whether to save a policy rule automatically to the adapter when it is added or removed.
func (e *Enforcer) EnableAutoSave(autoSave bool) {
e.autoSave = autoSave
}
// EnableAutoBuildRoleLinks controls whether to rebuild the role inheritance relations when a role is added or deleted.
func (e *Enforcer) EnableAutoBuildRoleLinks(autoBuildRoleLinks bool) {
e.autoBuildRoleLinks = autoBuildRoleLinks
}
// EnableAcceptJsonRequest controls whether to accept json as a request parameter.
func (e *Enforcer) EnableAcceptJsonRequest(acceptJsonRequest bool) {
e.acceptJsonRequest = acceptJsonRequest
}
// BuildRoleLinks manually rebuild the role inheritance relations.
func (e *Enforcer) BuildRoleLinks() error {
e.invalidateMatcherMap()
if e.rmMap == nil {
return errors.New("rmMap is nil")
}
for _, rm := range e.rmMap {
err := rm.Clear()
if err != nil {
return err
}
}
return e.model.BuildRoleLinks(e.rmMap)
}
// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
func (e *Enforcer) BuildIncrementalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error {
e.invalidateMatcherMap()
return e.model.BuildIncrementalRoleLinks(e.rmMap, op, "g", ptype, rules)
}
// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations with conditions.
func (e *Enforcer) BuildIncrementalConditionalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error {
e.invalidateMatcherMap()
return e.model.BuildIncrementalConditionalRoleLinks(e.condRmMap, op, "g", ptype, rules)
}
// NewEnforceContext Create a default structure based on the suffix.
func NewEnforceContext(suffix string) EnforceContext {
return EnforceContext{
RType: "r" + suffix,
PType: "p" + suffix,
EType: "e" + suffix,
MType: "m" + suffix,
}
}
func (e *Enforcer) invalidateMatcherMap() {
e.matcherMap = sync.Map{}
}
// enforce use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interface{}) (ok bool, err error) { //nolint:funlen,cyclop,gocyclo // TODO: reduce function complexity
logEntry := e.onLogBeforeEventInEnforce(rvals)
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
if e.logger != nil && logEntry != nil {
logEntry.Error = err
}
}
e.onLogAfterEventInEnforce(logEntry, ok)
}()
if !e.enabled {
return true, nil
}
functions := e.fm.GetFunctions()
if _, ok := e.model["g"]; ok {
for key, ast := range e.model["g"] {
// g must be a normal role definition (ast.RM != nil)
// or a conditional role definition (ast.CondRM != nil)
// ast.RM and ast.CondRM shouldn't be nil at the same time
if ast.RM != nil {
functions[key] = util.GenerateGFunction(ast.RM)
}
if ast.CondRM != nil {
functions[key] = util.GenerateConditionalGFunction(ast.CondRM)
}
}
}
var (
rType = "r"
pType = "p"
eType = "e"
mType = "m"
)
if len(rvals) != 0 {
switch rvals[0].(type) {
case EnforceContext:
enforceContext := rvals[0].(EnforceContext)
rType = enforceContext.RType
pType = enforceContext.PType
eType = enforceContext.EType
mType = enforceContext.MType
rvals = rvals[1:]
default:
break
}
}
var expString string
if matcher == "" {
expString = e.model["m"][mType].Value
} else {
// For custom matchers provided at runtime, escape backslashes in string literals
expString = util.EscapeStringLiterals(util.RemoveComments(util.EscapeAssertion(matcher)))
}
rTokens := make(map[string]int, len(e.model["r"][rType].Tokens))
for i, token := range e.model["r"][rType].Tokens {
rTokens[token] = i
}
pTokens := make(map[string]int, len(e.model["p"][pType].Tokens))
for i, token := range e.model["p"][pType].Tokens {
pTokens[token] = i
}
if e.acceptJsonRequest {
// try to parse all request values from json to map[string]interface{}
for i, rval := range rvals {
switch rval := rval.(type) {
case string:
// Only attempt JSON parsing for strings that look like JSON objects or arrays
if len(rval) > 0 && (rval[0] == '{' || rval[0] == '[') {
var mapValue map[string]interface{}
mapValue, err = util.JsonToMap(rval)
if err != nil {
// Return a clear error when JSON-like string fails to parse
return false, fmt.Errorf("failed to parse JSON parameter at index %d: %w", i, err)
}
rvals[i] = mapValue
}
}
}
}
parameters := enforceParameters{
rTokens: rTokens,
rVals: rvals,
pTokens: pTokens,
}
hasEval := util.HasEval(expString)
if hasEval {
functions["eval"] = generateEvalFunction(functions, ¶meters)
}
var expression *govaluate.EvaluableExpression
expression, err = e.getAndStoreMatcherExpression(hasEval, expString, functions)
if err != nil {
return false, err
}
if len(e.model["r"][rType].Tokens) != len(rvals) {
return false, fmt.Errorf(
"invalid request size: expected %d, got %d, rvals: %v",
len(e.model["r"][rType].Tokens),
len(rvals),
rvals)
}
var policyEffects []effector.Effect
var matcherResults []float64
var effect effector.Effect
var explainIndex int
if policyLen := len(e.model["p"][pType].Policy); policyLen != 0 && strings.Contains(expString, pType+"_") { //nolint:nestif // TODO: reduce function complexity
policyEffects = make([]effector.Effect, policyLen)
matcherResults = make([]float64, policyLen)
for policyIndex, pvals := range e.model["p"][pType].Policy {
// log.LogPrint("Policy Rule: ", pvals)
if len(e.model["p"][pType].Tokens) != len(pvals) {
return false, fmt.Errorf(
"invalid policy size: expected %d, got %d, pvals: %v",
len(e.model["p"][pType].Tokens),
len(pvals),
pvals)
}
parameters.pVals = pvals
result, err := expression.Eval(parameters)
// log.LogPrint("Result: ", result)
if err != nil {
return false, err
}
// set to no-match at first
matcherResults[policyIndex] = 0
switch result := result.(type) {
case bool:
if result {
matcherResults[policyIndex] = 1
}
case float64:
if result != 0 {
matcherResults[policyIndex] = 1
}
default:
return false, errors.New("matcher result should be bool, int or float")
}
if j, ok := parameters.pTokens[pType+"_eft"]; ok {
eft := parameters.pVals[j]
if eft == "allow" {
policyEffects[policyIndex] = effector.Allow
} else if eft == "deny" {
policyEffects[policyIndex] = effector.Deny
} else {
policyEffects[policyIndex] = effector.Indeterminate
}
} else {
policyEffects[policyIndex] = effector.Allow
}
// if e.model["e"]["e"].Value == "priority(p_eft) || deny" {
// break
// }
effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects, matcherResults, policyIndex, policyLen)
if err != nil {
return false, err
}
if effect != effector.Indeterminate {
break
}
}
} else {
if hasEval && len(e.model["p"][pType].Policy) == 0 {
return false, errors.New("please make sure rule exists in policy when using eval() in matcher")
}
policyEffects = make([]effector.Effect, 1)
matcherResults = make([]float64, 1)
matcherResults[0] = 1
parameters.pVals = make([]string, len(parameters.pTokens))
result, err := expression.Eval(parameters)
if err != nil {
return false, err
}
if result.(bool) {
policyEffects[0] = effector.Allow
} else {
policyEffects[0] = effector.Indeterminate
}
effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects, matcherResults, 0, 1)
if err != nil {
return false, err
}
}
if explains != nil {
if explainIndex != -1 && len(e.model["p"][pType].Policy) > explainIndex {
*explains = e.model["p"][pType].Policy[explainIndex]
}
}
// effect -> result
result := false
if effect == effector.Allow {
result = true
}
return result, nil
}
func (e *Enforcer) getAndStoreMatcherExpression(hasEval bool, expString string, functions map[string]govaluate.ExpressionFunction) (*govaluate.EvaluableExpression, error) {
var expression *govaluate.EvaluableExpression
var err error
var cachedExpression, isPresent = e.matcherMap.Load(expString)
if !hasEval && isPresent {
expression = cachedExpression.(*govaluate.EvaluableExpression)
} else {
expression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
if err != nil {
return nil, err
}
e.matcherMap.Store(expString, expression)
}
return expression, nil
}
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
func (e *Enforcer) Enforce(rvals ...interface{}) (bool, error) {
return e.enforce("", nil, rvals...)
}
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
func (e *Enforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) {
return e.enforce(matcher, nil, rvals...)
}
// EnforceEx explain enforcement by informing matched rules.
func (e *Enforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) {
explain := []string{}
result, err := e.enforce("", &explain, rvals...)
return result, explain, err
}
// EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules.
func (e *Enforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) {
explain := []string{}
result, err := e.enforce(matcher, &explain, rvals...)
return result, explain, err
}
// BatchEnforce enforce in batches.
func (e *Enforcer) BatchEnforce(requests [][]interface{}) ([]bool, error) {
var results []bool
for _, request := range requests {
result, err := e.enforce("", nil, request...)
if err != nil {
return results, err
}
results = append(results, result)
}
return results, nil
}
// BatchEnforceWithMatcher enforce with matcher in batches.
func (e *Enforcer) BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error) {
var results []bool
for _, request := range requests {
result, err := e.enforce(matcher, nil, request...)
if err != nil {
return results, err
}
results = append(results, result)
}
return results, nil
}
// AddNamedMatchingFunc add MatchingFunc by ptype RoleManager.
func (e *Enforcer) AddNamedMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool {
if rm, ok := e.rmMap[ptype]; ok {
rm.AddMatchingFunc(name, fn)
return true
}
return false
}
// AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager.
func (e *Enforcer) AddNamedDomainMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool {
if rm, ok := e.rmMap[ptype]; ok {
rm.AddDomainMatchingFunc(name, fn)
return true
}
if condRm, ok := e.condRmMap[ptype]; ok {
condRm.AddDomainMatchingFunc(name, fn)
return true
}
return false
}
// AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName,
// when fn returns true, Link is valid, otherwise invalid.
func (e *Enforcer) AddNamedLinkConditionFunc(ptype, user, role string, fn rbac.LinkConditionFunc) bool {
if rm, ok := e.condRmMap[ptype]; ok {
rm.AddLinkConditionFunc(user, role, fn)
return true
}
return false
}
// AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},
// when fn returns true, Link is valid, otherwise invalid.
func (e *Enforcer) AddNamedDomainLinkConditionFunc(ptype, user, role string, domain string, fn rbac.LinkConditionFunc) bool {
if rm, ok := e.condRmMap[ptype]; ok {
rm.AddDomainLinkConditionFunc(user, role, domain, fn)
return true
}
return false
}
// SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName.
func (e *Enforcer) SetNamedLinkConditionFuncParams(ptype, user, role string, params ...string) bool {
if rm, ok := e.condRmMap[ptype]; ok {
rm.SetLinkConditionFuncParams(user, role, params...)
return true
}
return false
}
// SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn
// for Link userName->{roleName, domain}.
func (e *Enforcer) SetNamedDomainLinkConditionFuncParams(ptype, user, role, domain string, params ...string) bool {
if rm, ok := e.condRmMap[ptype]; ok {
rm.SetDomainLinkConditionFuncParams(user, role, domain, params...)
return true
}
return false
}
// assumes bounds have already been checked.
type enforceParameters struct {
rTokens map[string]int
rVals []interface{}
pTokens map[string]int
pVals []string
}
// implements govaluate.Parameters.
func (p enforceParameters) Get(name string) (interface{}, error) {
if name == "" {
return nil, nil
}
switch name[0] {
case 'p':
i, ok := p.pTokens[name]
if !ok {
return nil, errors.New("No parameter '" + name + "' found.")
}
return p.pVals[i], nil
case 'r':
i, ok := p.rTokens[name]
if !ok {
return nil, errors.New("No parameter '" + name + "' found.")
}
return p.rVals[i], nil
default:
return nil, errors.New("No parameter '" + name + "' found.")
}
}
func generateEvalFunction(functions map[string]govaluate.ExpressionFunction, parameters *enforceParameters) govaluate.ExpressionFunction {
return func(args ...interface{}) (interface{}, error) {
if len(args) != 1 {
return nil, fmt.Errorf("function eval(subrule string) expected %d arguments, but got %d", 1, len(args))
}
expression, ok := args[0].(string)
if !ok {
return nil, errors.New("argument of eval(subrule string) must be a string")
}
expression = util.EscapeAssertion(expression)
expr, err := govaluate.NewEvaluableExpressionWithFunctions(expression, functions)
if err != nil {
return nil, fmt.Errorf("error while parsing eval parameter: %s, %s", expression, err.Error())
}
return expr.Eval(parameters)
}
}
================================================
FILE: enforcer_backslash_test.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
"github.com/casbin/casbin/v3/model"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
)
// TestBackslashHandlingConsistency tests that backslashes in string literals
// within matcher expressions are handled consistently with CSV-parsed values.
// This addresses the issue where govaluate interprets escape sequences in
// string literals, but CSV parsing treats backslashes as literal characters.
func TestBackslashHandlingConsistency(t *testing.T) {
// Test case 1: Literal string in matcher should match CSV-parsed request
t.Run("LiteralInMatcher", func(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
// User writes '\1\2' in matcher - should be treated as literal backslashes
m.AddDef("m", "m", "regexMatch('\\1\\2', p.obj)")
e, err := NewEnforcer(m, fileadapter.NewAdapter("examples/basic_policy.csv"))
if err != nil {
t.Fatal(err)
}
// Add a policy with a regex pattern containing backslashes
// CSV format: "\\[0-9]+\\" means literal string with 4 backslashes
_, err = e.AddPolicy("filename", "\\\\[0-9]+\\\\", "read")
if err != nil {
t.Fatal(err)
}
// This should match because '\1\2' after escaping becomes \1\2,
// and the pattern \\[0-9]+\\ matches strings like \1\ (which is a substring of \1\2)
result, err := e.Enforce("filename", "dummy", "read")
if err != nil {
t.Fatal(err)
}
if !result {
t.Errorf("Expected true, got false - literal '\\1\\2' should match after escape processing")
}
})
// Test case 2: Request parameter should match policy with same backslash content
t.Run("RequestParameterVsPolicy", func(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "regexMatch(r.obj, p.obj)")
e, err := NewEnforcer(m, fileadapter.NewAdapter("examples/basic_policy.csv"))
if err != nil {
t.Fatal(err)
}
// Add policy with regex pattern
_, err = e.AddPolicy("filename", "\\\\[0-9]+\\\\", "read")
if err != nil {
t.Fatal(err)
}
// Request with backslashes - simulating CSV input "\1\2" which becomes \1\2
result, err := e.Enforce("filename", "\\1\\2", "read")
if err != nil {
t.Fatal(err)
}
if !result {
t.Errorf("Expected true, got false - request \\1\\2 should match regex pattern")
}
})
// Test case 3: Both approaches should give the same result
t.Run("ConsistencyBetweenLiteralAndParameter", func(t *testing.T) {
// Create two enforcers with different matchers
m1 := model.NewModel()
m1.AddDef("r", "r", "sub, obj, act")
m1.AddDef("p", "p", "sub, obj, act")
m1.AddDef("e", "e", "some(where (p.eft == allow))")
m1.AddDef("m", "m", "regexMatch('\\1\\2', p.obj)")
m2 := model.NewModel()
m2.AddDef("r", "r", "sub, obj, act")
m2.AddDef("p", "p", "sub, obj, act")
m2.AddDef("e", "e", "some(where (p.eft == allow))")
m2.AddDef("m", "m", "regexMatch(r.obj, p.obj)")
e1, err := NewEnforcer(m1, fileadapter.NewAdapter("examples/basic_policy.csv"))
if err != nil {
t.Fatal(err)
}
e2, err := NewEnforcer(m2, fileadapter.NewAdapter("examples/basic_policy.csv"))
if err != nil {
t.Fatal(err)
}
// Add same policy to both
pattern := "\\\\[0-9]+\\\\"
_, err = e1.AddPolicy("filename", pattern, "read")
if err != nil {
t.Fatal(err)
}
_, err = e2.AddPolicy("filename", pattern, "read")
if err != nil {
t.Fatal(err)
}
// Test with the same request
result1, err := e1.Enforce("filename", "dummy", "read")
if err != nil {
t.Fatal(err)
}
result2, err := e2.Enforce("filename", "\\1\\2", "read")
if err != nil {
t.Fatal(err)
}
if result1 != result2 {
t.Errorf("Inconsistent results: literal in matcher gave %v, parameter gave %v", result1, result2)
}
})
// Test case 4: Simple equality check with backslashes
t.Run("SimpleEqualityWithBackslashes", func(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
// In Go source, '\test' (one backslash in the actual string) represents
// what would be typed in a web form. After escape processing, it will match
// the CSV-parsed value "\test" (one backslash).
// Note: In Go source, we write "r.obj == '\\test'" which is a Go string
// containing the text: r.obj == '\test' (with ONE backslash in the string content)
m.AddDef("m", "m", "r.obj == '\\test' && p.sub == r.sub")
e, err := NewEnforcer(m, fileadapter.NewAdapter("examples/basic_policy.csv"))
if err != nil {
t.Fatal(err)
}
_, err = e.AddPolicy("alice", "any", "read")
if err != nil {
t.Fatal(err)
}
// Request with literal backslash from CSV would be "\test"
// In Go source, we write "\\test" which represents the string \test (one backslash)
result, err := e.Enforce("alice", "\\test", "read")
if err != nil {
t.Fatal(err)
}
if !result {
t.Errorf("Expected true - literal '\\test' should equal request parameter \\test")
}
})
}
================================================
FILE: enforcer_cached.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/casbin/casbin/v3/persist/cache"
)
// CachedEnforcer wraps Enforcer and provides decision cache.
type CachedEnforcer struct {
*Enforcer
expireTime time.Duration
cache cache.Cache
enableCache int32
locker *sync.RWMutex
}
type CacheableParam interface {
GetCacheKey() string
}
// NewCachedEnforcer creates a cached enforcer via file or DB.
func NewCachedEnforcer(params ...interface{}) (*CachedEnforcer, error) {
e := &CachedEnforcer{}
var err error
e.Enforcer, err = NewEnforcer(params...)
if err != nil {
return nil, err
}
e.enableCache = 1
e.cache, _ = cache.NewDefaultCache()
e.locker = new(sync.RWMutex)
return e, nil
}
// EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.
func (e *CachedEnforcer) EnableCache(enableCache bool) {
var enabled int32
if enableCache {
enabled = 1
}
atomic.StoreInt32(&e.enableCache, enabled)
}
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
// if rvals is not string , ignore the cache.
func (e *CachedEnforcer) Enforce(rvals ...interface{}) (bool, error) {
if atomic.LoadInt32(&e.enableCache) == 0 {
return e.Enforcer.Enforce(rvals...)
}
key, ok := e.getKey(rvals...)
if !ok {
return e.Enforcer.Enforce(rvals...)
}
if res, err := e.getCachedResult(key); err == nil {
return res, nil
} else if err != cache.ErrNoSuchKey {
return res, err
}
res, err := e.Enforcer.Enforce(rvals...)
if err != nil {
return false, err
}
err = e.setCachedResult(key, res, e.expireTime)
return res, err
}
func (e *CachedEnforcer) LoadPolicy() error {
if atomic.LoadInt32(&e.enableCache) != 0 {
if err := e.cache.Clear(); err != nil {
return err
}
}
return e.Enforcer.LoadPolicy()
}
func (e *CachedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {
if atomic.LoadInt32(&e.enableCache) != 0 {
key, ok := e.getKey(params...)
if ok {
if err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {
return false, err
}
}
}
return e.Enforcer.RemovePolicy(params...)
}
func (e *CachedEnforcer) RemovePolicies(rules [][]string) (bool, error) {
if len(rules) != 0 {
if atomic.LoadInt32(&e.enableCache) != 0 {
irule := make([]interface{}, len(rules[0]))
for _, rule := range rules {
for i, param := range rule {
irule[i] = param
}
key, _ := e.getKey(irule...)
if err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {
return false, err
}
}
}
}
return e.Enforcer.RemovePolicies(rules)
}
func (e *CachedEnforcer) getCachedResult(key string) (res bool, err error) {
e.locker.Lock()
defer e.locker.Unlock()
return e.cache.Get(key)
}
func (e *CachedEnforcer) SetExpireTime(expireTime time.Duration) {
e.expireTime = expireTime
}
func (e *CachedEnforcer) SetCache(c cache.Cache) {
e.cache = c
}
func (e *CachedEnforcer) setCachedResult(key string, res bool, extra ...interface{}) error {
e.locker.Lock()
defer e.locker.Unlock()
return e.cache.Set(key, res, extra...)
}
func (e *CachedEnforcer) getKey(params ...interface{}) (string, bool) {
return GetCacheKey(params...)
}
// InvalidateCache deletes all the existing cached decisions.
func (e *CachedEnforcer) InvalidateCache() error {
e.locker.Lock()
defer e.locker.Unlock()
return e.cache.Clear()
}
func GetCacheKey(params ...interface{}) (string, bool) {
key := strings.Builder{}
for _, param := range params {
switch typedParam := param.(type) {
case string:
key.WriteString(typedParam)
case CacheableParam:
key.WriteString(typedParam.GetCacheKey())
default:
return "", false
}
key.WriteString("$$")
}
return key.String(), true
}
// ClearPolicy clears all policy.
func (e *CachedEnforcer) ClearPolicy() {
if atomic.LoadInt32(&e.enableCache) != 0 {
if err := e.cache.Clear(); err != nil {
// Logger has been removed - error is ignored
return
}
}
e.Enforcer.ClearPolicy()
}
================================================
FILE: enforcer_cached_b_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"testing"
)
func BenchmarkCachedRaw(b *testing.B) {
for i := 0; i < b.N; i++ {
rawEnforce("alice", "data1", "read")
}
}
func BenchmarkCachedBasicModel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkCachedRBACModel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data2", "read")
}
}
func BenchmarkCachedRBACModelSmall(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_model.conf")
// 100 roles, 10 resources.
for i := 0; i < 100; i++ {
_, err := e.AddPolicy(fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read")
if err != nil {
b.Fatal(err)
}
}
// 1000 users.
for i := 0; i < 1000; i++ {
_, err := e.AddGroupingPolicy(fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10))
if err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user501", "data9", "read")
}
}
func BenchmarkCachedRBACModelMedium(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_model.conf")
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 10000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user5001", "data150", "read")
}
}
func BenchmarkCachedRBACModelLarge(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 100000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 100000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user50001", "data1500", "read")
}
}
func BenchmarkCachedRBACModelWithResourceRoles(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_with_resource_roles_model.conf", "examples/rbac_with_resource_roles_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkCachedRBACModelWithDomains(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "domain1", "data1", "read")
}
}
func BenchmarkCachedABACModel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/abac_model.conf")
data1 := newTestResource("data1", "alice")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", data1, "read")
}
}
func BenchmarkCachedKeyMatchModel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/keymatch_model.conf", "examples/keymatch_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "/alice_data/resource1", "GET")
}
}
func BenchmarkCachedRBACModelWithDeny(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_with_deny_model.conf", "examples/rbac_with_deny_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkCachedPriorityModel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/priority_model.conf", "examples/priority_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkCachedWithEnforceContext(b *testing.B) {
e, _ := NewCachedEnforcer("examples/priority_model_enforce_context.conf", "examples/priority_policy_enforce_context.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce(EnforceContext{RType: "r2", PType: "p", EType: "e", MType: "m2"}, "alice", "data1")
}
}
func BenchmarkCachedRBACModelMediumParallel(b *testing.B) {
e, _ := NewCachedEnforcer("examples/rbac_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 100000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 100000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = e.Enforce("user5001", "data150", "read")
}
})
}
================================================
FILE: enforcer_cached_gfunction_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import "testing"
// TestCachedGFunctionAfterAddingGroupingPolicy tests that adding new grouping policies
// at runtime correctly updates permission evaluation without requiring a restart or manual cache clearing.
// This is a regression test for the issue where GenerateGFunction's memoization cache
// caused stale permission results after adding new grouping policies.
func TestCachedGFunctionAfterAddingGroupingPolicy(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Initial state: bob should not have access to data2 (read)
// bob has no roles initially
ok, err := e.Enforce("bob", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed: %v", err)
}
if ok {
t.Error("bob should not have read access to data2 initially")
}
// Add a new grouping policy: bob becomes data2_admin
// data2_admin has read and write access to data2
_, err = e.AddGroupingPolicy("bob", "data2_admin")
if err != nil {
t.Fatalf("Failed to add grouping policy: %v", err)
}
// Now bob should have read access to data2 through the data2_admin role
// This should work immediately without needing to clear cache or restart
ok, err = e.Enforce("bob", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed after adding grouping policy: %v", err)
}
if !ok {
t.Error("bob should have read access to data2 after being added to data2_admin role")
}
// Also verify write access works
ok, err = e.Enforce("bob", "data2", "write")
if err != nil {
t.Fatalf("Enforce failed: %v", err)
}
if !ok {
t.Error("bob should have write access to data2 after being added to data2_admin role")
}
}
// TestCachedGFunctionWithMultipleEnforceCalls tests that the g() function cache
// properly invalidates when grouping policies change, even after multiple enforce calls.
func TestCachedGFunctionWithMultipleEnforceCalls(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Make multiple enforce calls to ensure the g() function closure is cached
for i := 0; i < 5; i++ {
ok, enforceErr := e.Enforce("charlie", "data2", "read")
if enforceErr != nil {
t.Fatalf("Enforce failed on iteration %d: %v", i, enforceErr)
}
if ok {
t.Errorf("charlie should not have read access to data2 on iteration %d", i)
}
}
// Add grouping policy
_, err = e.AddGroupingPolicy("charlie", "data2_admin")
if err != nil {
t.Fatalf("Failed to add grouping policy: %v", err)
}
// Immediately verify the change took effect
ok, err := e.Enforce("charlie", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed after adding grouping policy: %v", err)
}
if !ok {
t.Error("charlie should have read access to data2 immediately after being added to data2_admin role")
}
// Make multiple calls to ensure it stays consistent
for i := 0; i < 5; i++ {
ok, enforceErr := e.Enforce("charlie", "data2", "read")
if enforceErr != nil {
t.Fatalf("Enforce failed on iteration %d after policy change: %v", i, enforceErr)
}
if !ok {
t.Errorf("charlie should have read access to data2 on iteration %d after policy change", i)
}
}
}
// TestCachedGFunctionAfterRemovingGroupingPolicy tests that removing grouping policies
// also properly invalidates the g() function cache.
func TestCachedGFunctionAfterRemovingGroupingPolicy(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// alice initially has the data2_admin role
ok, err := e.Enforce("alice", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed: %v", err)
}
if !ok {
t.Error("alice should have read access to data2 initially")
}
// Remove alice from data2_admin role
_, err = e.RemoveGroupingPolicy("alice", "data2_admin")
if err != nil {
t.Fatalf("Failed to remove grouping policy: %v", err)
}
// Now alice should not have access to data2 (she only has access to data1)
ok, err = e.Enforce("alice", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed after removing grouping policy: %v", err)
}
if ok {
t.Error("alice should not have read access to data2 after being removed from data2_admin role")
}
// Verify alice still has access to data1 (direct policy)
ok, err = e.Enforce("alice", "data1", "read")
if err != nil {
t.Fatalf("Enforce failed: %v", err)
}
if !ok {
t.Error("alice should still have read access to data1 (direct policy)")
}
}
// TestCachedGFunctionAfterBuildRoleLinks tests the specific scenario mentioned in the bug report:
// adding grouping policies and calling BuildRoleLinks() manually should properly invalidate the cache.
func TestCachedGFunctionAfterBuildRoleLinks(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// First, make some enforce calls to ensure the g() function closure is created and cached
// This will cache "bob" NOT having data2_admin role in the g() function's sync.Map
for i := 0; i < 3; i++ {
ok, enforceErr := e.Enforce("bob", "data2", "read")
if enforceErr != nil {
t.Fatalf("Enforce failed on iteration %d: %v", i, enforceErr)
}
if ok {
t.Errorf("bob should not have read access to data2 on iteration %d (before adding role)", i)
}
}
// Disable autoBuildRoleLinks to manually control when role links are rebuilt
e.EnableAutoBuildRoleLinks(false)
// Manually add the grouping policy to the model (bypassing BuildIncrementalRoleLinks)
// This simulates the scenario where policies are loaded from database
err = e.model.AddPolicy("g", "g", []string{"bob", "data2_admin"})
if err != nil {
t.Fatalf("Failed to add grouping policy to model: %v", err)
}
// Manually build role links as mentioned in the issue
// This is the key part - BuildRoleLinks() should invalidate the matcher map cache
err = e.BuildRoleLinks()
if err != nil {
t.Fatalf("Failed to build role links: %v", err)
}
// Now bob should have read access to data2 through the data2_admin role
// This is where the bug would manifest - if BuildRoleLinks() doesn't invalidate the cache,
// the old g() function closure with "bob->data2_admin = false" cached will still be used
ok, err := e.Enforce("bob", "data2", "read")
if err != nil {
t.Fatalf("Enforce failed after BuildRoleLinks: %v", err)
}
if !ok {
t.Error("bob should have read access to data2 after BuildRoleLinks() - this indicates the g() function cache was not properly invalidated")
}
}
================================================
FILE: enforcer_cached_synced.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sync"
"sync/atomic"
"time"
"github.com/casbin/casbin/v3/persist/cache"
)
// SyncedCachedEnforcer wraps Enforcer and provides decision sync cache.
type SyncedCachedEnforcer struct {
*SyncedEnforcer
expireTime time.Duration
cache cache.Cache
enableCache int32
locker *sync.RWMutex
}
// NewSyncedCachedEnforcer creates a sync cached enforcer via file or DB.
func NewSyncedCachedEnforcer(params ...interface{}) (*SyncedCachedEnforcer, error) {
e := &SyncedCachedEnforcer{}
var err error
e.SyncedEnforcer, err = NewSyncedEnforcer(params...)
if err != nil {
return nil, err
}
e.enableCache = 1
e.cache, _ = cache.NewSyncCache()
e.locker = new(sync.RWMutex)
return e, nil
}
// EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.
func (e *SyncedCachedEnforcer) EnableCache(enableCache bool) {
var enabled int32
if enableCache {
enabled = 1
}
atomic.StoreInt32(&e.enableCache, enabled)
}
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
// if rvals is not string , ignore the cache.
func (e *SyncedCachedEnforcer) Enforce(rvals ...interface{}) (bool, error) {
if atomic.LoadInt32(&e.enableCache) == 0 {
return e.SyncedEnforcer.Enforce(rvals...)
}
key, ok := e.getKey(rvals...)
if !ok {
return e.SyncedEnforcer.Enforce(rvals...)
}
if res, err := e.getCachedResult(key); err == nil {
return res, nil
} else if err != cache.ErrNoSuchKey {
return res, err
}
res, err := e.SyncedEnforcer.Enforce(rvals...)
if err != nil {
return false, err
}
err = e.setCachedResult(key, res, e.expireTime)
return res, err
}
func (e *SyncedCachedEnforcer) LoadPolicy() error {
if atomic.LoadInt32(&e.enableCache) != 0 {
if err := e.cache.Clear(); err != nil {
return err
}
}
return e.SyncedEnforcer.LoadPolicy()
}
func (e *SyncedCachedEnforcer) AddPolicy(params ...interface{}) (bool, error) {
if ok, err := e.checkOneAndRemoveCache(params...); !ok {
return ok, err
}
return e.SyncedEnforcer.AddPolicy(params...)
}
func (e *SyncedCachedEnforcer) AddPolicies(rules [][]string) (bool, error) {
if ok, err := e.checkManyAndRemoveCache(rules); !ok {
return ok, err
}
return e.SyncedEnforcer.AddPolicies(rules)
}
func (e *SyncedCachedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {
if ok, err := e.checkOneAndRemoveCache(params...); !ok {
return ok, err
}
return e.SyncedEnforcer.RemovePolicy(params...)
}
func (e *SyncedCachedEnforcer) RemovePolicies(rules [][]string) (bool, error) {
if ok, err := e.checkManyAndRemoveCache(rules); !ok {
return ok, err
}
return e.SyncedEnforcer.RemovePolicies(rules)
}
func (e *SyncedCachedEnforcer) getCachedResult(key string) (res bool, err error) {
return e.cache.Get(key)
}
func (e *SyncedCachedEnforcer) SetExpireTime(expireTime time.Duration) {
e.locker.Lock()
defer e.locker.Unlock()
e.expireTime = expireTime
}
// SetCache need to be sync cache.
func (e *SyncedCachedEnforcer) SetCache(c cache.Cache) {
e.locker.Lock()
defer e.locker.Unlock()
e.cache = c
}
func (e *SyncedCachedEnforcer) setCachedResult(key string, res bool, extra ...interface{}) error {
return e.cache.Set(key, res, extra...)
}
func (e *SyncedCachedEnforcer) getKey(params ...interface{}) (string, bool) {
return GetCacheKey(params...)
}
// InvalidateCache deletes all the existing cached decisions.
func (e *SyncedCachedEnforcer) InvalidateCache() error {
return e.cache.Clear()
}
func (e *SyncedCachedEnforcer) checkOneAndRemoveCache(params ...interface{}) (bool, error) {
if atomic.LoadInt32(&e.enableCache) != 0 {
key, ok := e.getKey(params...)
if ok {
if err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {
return false, err
}
}
}
return true, nil
}
func (e *SyncedCachedEnforcer) checkManyAndRemoveCache(rules [][]string) (bool, error) {
if len(rules) != 0 {
if atomic.LoadInt32(&e.enableCache) != 0 {
irule := make([]interface{}, len(rules[0]))
for _, rule := range rules {
for i, param := range rule {
irule[i] = param
}
key, _ := e.getKey(irule...)
if err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {
return false, err
}
}
}
}
return true, nil
}
================================================
FILE: enforcer_cached_synced_test.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sync"
"testing"
"time"
)
func testSyncEnforceCache(t *testing.T, e *SyncedCachedEnforcer, sub string, obj interface{}, act string, res bool) {
t.Helper()
if myRes, _ := e.Enforce(sub, obj, act); myRes != res {
t.Errorf("%s, %v, %s: %t, supposed to be %t", sub, obj, act, myRes, res)
}
}
func TestSyncCache(t *testing.T) {
e, _ := NewSyncedCachedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e.expireTime = time.Millisecond
// The cache is enabled by default for NewCachedEnforcer.
g := sync.WaitGroup{}
goThread := 1000
g.Add(goThread)
for i := 0; i < goThread; i++ {
go func() {
_, _ = e.AddPolicy("alice", "data2", "read")
testSyncEnforceCache(t, e, "alice", "data2", "read", true)
if e.InvalidateCache() != nil {
panic("never reached")
}
g.Done()
}()
}
g.Wait()
_, _ = e.RemovePolicy("alice", "data2", "read")
testSyncEnforceCache(t, e, "alice", "data1", "read", true)
time.Sleep(time.Millisecond * 2) // coverage for expire
testSyncEnforceCache(t, e, "alice", "data1", "read", true)
testSyncEnforceCache(t, e, "alice", "data1", "write", false)
testSyncEnforceCache(t, e, "alice", "data2", "read", false)
testSyncEnforceCache(t, e, "alice", "data2", "write", false)
// The cache is enabled, calling RemovePolicy, LoadPolicy or RemovePolicies will
// also operate cached items.
_, _ = e.RemovePolicy("alice", "data1", "read")
testSyncEnforceCache(t, e, "alice", "data1", "read", false)
testSyncEnforceCache(t, e, "alice", "data1", "write", false)
testSyncEnforceCache(t, e, "alice", "data2", "read", false)
testSyncEnforceCache(t, e, "alice", "data2", "write", false)
e, _ = NewSyncedCachedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testSyncEnforceCache(t, e, "alice", "data1", "read", true)
testSyncEnforceCache(t, e, "bob", "data2", "write", true)
testSyncEnforceCache(t, e, "alice", "data2", "read", true)
testSyncEnforceCache(t, e, "alice", "data2", "write", true)
_, _ = e.RemovePolicies([][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
})
testSyncEnforceCache(t, e, "alice", "data1", "read", false)
testSyncEnforceCache(t, e, "bob", "data2", "write", false)
testSyncEnforceCache(t, e, "alice", "data2", "read", true)
testSyncEnforceCache(t, e, "alice", "data2", "write", true)
}
================================================
FILE: enforcer_cached_test.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import "testing"
func testEnforceCache(t *testing.T, e *CachedEnforcer, sub string, obj interface{}, act string, res bool) {
t.Helper()
if myRes, _ := e.Enforce(sub, obj, act); myRes != res {
t.Errorf("%s, %v, %s: %t, supposed to be %t", sub, obj, act, myRes, res)
}
}
func TestCache(t *testing.T) {
e, _ := NewCachedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
// The cache is enabled by default for NewCachedEnforcer.
testEnforceCache(t, e, "alice", "data1", "read", true)
testEnforceCache(t, e, "alice", "data1", "write", false)
testEnforceCache(t, e, "alice", "data2", "read", false)
testEnforceCache(t, e, "alice", "data2", "write", false)
// The cache is enabled, calling RemovePolicy, LoadPolicy or RemovePolicies will
// also operate cached items.
_, _ = e.RemovePolicy("alice", "data1", "read")
testEnforceCache(t, e, "alice", "data1", "read", false)
testEnforceCache(t, e, "alice", "data1", "write", false)
testEnforceCache(t, e, "alice", "data2", "read", false)
testEnforceCache(t, e, "alice", "data2", "write", false)
e, _ = NewCachedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testEnforceCache(t, e, "alice", "data1", "read", true)
testEnforceCache(t, e, "bob", "data2", "write", true)
testEnforceCache(t, e, "alice", "data2", "read", true)
testEnforceCache(t, e, "alice", "data2", "write", true)
_, _ = e.RemovePolicies([][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
})
testEnforceCache(t, e, "alice", "data1", "read", false)
testEnforceCache(t, e, "bob", "data2", "write", false)
testEnforceCache(t, e, "alice", "data2", "read", true)
testEnforceCache(t, e, "alice", "data2", "write", true)
e, _ = NewCachedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testEnforceCache(t, e, "alice", "data1", "read", true)
testEnforceCache(t, e, "bob", "data2", "write", true)
testEnforceCache(t, e, "alice", "data2", "read", true)
testEnforceCache(t, e, "alice", "data2", "write", true)
e.ClearPolicy()
testEnforceCache(t, e, "alice", "data1", "read", false)
testEnforceCache(t, e, "bob", "data2", "write", false)
testEnforceCache(t, e, "alice", "data2", "read", false)
testEnforceCache(t, e, "alice", "data2", "write", false)
}
================================================
FILE: enforcer_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"errors"
"fmt"
Err "github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// ContextEnforcer wraps Enforcer and provides context-aware operations.
type ContextEnforcer struct {
*Enforcer
adapterCtx persist.ContextAdapter
}
// NewContextEnforcer creates a context-aware enforcer via file or DB.
func NewContextEnforcer(params ...interface{}) (IEnforcerContext, error) {
e := &ContextEnforcer{}
var err error
e.Enforcer, err = NewEnforcer(params...)
if err != nil {
return nil, err
}
if e.Enforcer.adapter != nil {
if contextAdapter, ok := e.Enforcer.adapter.(persist.ContextAdapter); ok {
e.adapterCtx = contextAdapter
} else {
return nil, errors.New("adapter does not support context operations, ContextAdapter interface not implemented")
}
} else {
return nil, errors.New("no adapter provided, ContextEnforcer requires a ContextAdapter")
}
return e, nil
}
// LoadPolicyCtx loads all policy rules from the storage with context.
func (e *ContextEnforcer) LoadPolicyCtx(ctx context.Context) error {
newModel, err := e.loadPolicyFromAdapterCtx(ctx, e.model)
if err != nil {
return err
}
err = e.applyModifiedModel(newModel)
if err != nil {
return err
}
return nil
}
func (e *ContextEnforcer) loadPolicyFromAdapterCtx(ctx context.Context, baseModel model.Model) (model.Model, error) {
newModel := baseModel.Copy()
newModel.ClearPolicy()
if err := e.adapterCtx.LoadPolicyCtx(ctx, newModel); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
return nil, err
}
if err := newModel.SortPoliciesBySubjectHierarchy(); err != nil {
return nil, err
}
if err := newModel.SortPoliciesByPriority(); err != nil {
return nil, err
}
return newModel, nil
}
// LoadFilteredPolicyCtx loads all policy rules from the storage with context and filter.
func (e *Enforcer) LoadFilteredPolicyCtx(ctx context.Context, filter interface{}) error {
e.model.ClearPolicy()
return e.loadFilteredPolicyCtx(ctx, filter)
}
// LoadIncrementalFilteredPolicyCtx append a filtered policy from file/database with context.
func (e *Enforcer) LoadIncrementalFilteredPolicyCtx(ctx context.Context, filter interface{}) error {
return e.loadFilteredPolicyCtx(ctx, filter)
}
func (e *Enforcer) loadFilteredPolicyCtx(ctx context.Context, filter interface{}) error {
e.invalidateMatcherMap()
var filteredAdapter persist.ContextFilteredAdapter
// Attempt to cast the Adapter as a FilteredAdapter
switch adapter := e.adapter.(type) {
case persist.ContextFilteredAdapter:
filteredAdapter = adapter
default:
return errors.New("filtered policies are not supported by this adapter")
}
if err := filteredAdapter.LoadFilteredPolicyCtx(ctx, e.model, filter); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
return err
}
if err := e.model.SortPoliciesBySubjectHierarchy(); err != nil {
return err
}
if err := e.model.SortPoliciesByPriority(); err != nil {
return err
}
e.initRmMap()
e.model.PrintPolicy()
if e.autoBuildRoleLinks {
err := e.BuildRoleLinks()
if err != nil {
return err
}
}
return nil
}
// IsFilteredCtx returns true if the loaded policy has been filtered with context.
func (e *ContextEnforcer) IsFilteredCtx(ctx context.Context) bool {
if adapter, ok := e.adapter.(persist.ContextFilteredAdapter); ok {
return adapter.IsFilteredCtx(ctx)
} else {
return false
}
}
func (e *ContextEnforcer) SavePolicyCtx(ctx context.Context) error {
if e.IsFiltered() {
return errors.New("cannot save a filtered policy")
}
if err := e.adapterCtx.SavePolicyCtx(ctx, e.model); err != nil {
return err
}
if e.watcher != nil {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForSavePolicy(e.model)
} else {
err = e.watcher.Update()
}
return err
}
return nil
}
// AddPolicyCtx adds a policy rule to the storage with context.
func (e *ContextEnforcer) AddPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {
return e.AddNamedPolicyCtx(ctx, "p", params...)
}
// AddPoliciesCtx adds policy rules to the storage with context.
func (e *ContextEnforcer) AddPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.AddNamedPoliciesCtx(ctx, "p", rules)
}
// AddNamedPolicyCtx adds a named policy rule to the storage with context.
func (e *ContextEnforcer) AddNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
strSlice = append(make([]string, 0, len(strSlice)), strSlice...)
return e.addPolicyCtx(ctx, "p", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.addPolicyCtx(ctx, "p", ptype, policy)
}
// AddNamedPoliciesCtx adds named policy rules to the storage with context.
func (e *ContextEnforcer) AddNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesCtx(ctx, "p", ptype, rules, false)
}
func (e *ContextEnforcer) AddPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.AddNamedPoliciesExCtx(ctx, "p", rules)
}
func (e *ContextEnforcer) AddNamedPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesCtx(ctx, "p", ptype, rules, true)
}
// RemovePolicyCtx removes a policy rule from the storage with context.
func (e *ContextEnforcer) RemovePolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {
return e.RemoveNamedPolicyCtx(ctx, "p", params...)
}
// RemoveNamedPolicyCtx removes a named policy rule from the storage with context.
func (e *ContextEnforcer) RemoveNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
return e.removePolicyCtx(ctx, "p", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.removePolicyCtx(ctx, "p", ptype, policy)
}
// RemovePoliciesCtx removes policy rules from the storage with context.
func (e *ContextEnforcer) RemovePoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.RemoveNamedPoliciesCtx(ctx, "p", rules)
}
// RemoveNamedPoliciesCtx removes named policy rules from the storage with context.
func (e *ContextEnforcer) RemoveNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.removePoliciesCtx(ctx, "p", ptype, rules)
}
// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
func (e *ContextEnforcer) RemoveFilteredPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error) {
return e.RemoveFilteredNamedPolicyCtx(ctx, "p", fieldIndex, fieldValues...)
}
// RemoveFilteredNamedPolicyCtx removes named policy rules that match the filter from the storage with context.
func (e *ContextEnforcer) RemoveFilteredNamedPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicyCtx(ctx, "p", ptype, fieldIndex, fieldValues)
}
// UpdatePolicyCtx updates a policy rule in the storage with context.
func (e *ContextEnforcer) UpdatePolicyCtx(ctx context.Context, oldPolicy []string, newPolicy []string) (bool, error) {
return e.UpdateNamedPolicyCtx(ctx, "p", oldPolicy, newPolicy)
}
// UpdateNamedPolicyCtx updates a named policy rule in the storage with context.
func (e *ContextEnforcer) UpdateNamedPolicyCtx(ctx context.Context, ptype string, p1 []string, p2 []string) (bool, error) {
return e.updatePolicyCtx(ctx, "p", ptype, p1, p2)
}
// UpdatePoliciesCtx updates policy rules in the storage with context.
func (e *ContextEnforcer) UpdatePoliciesCtx(ctx context.Context, oldPolicies [][]string, newPolicies [][]string) (bool, error) {
return e.UpdateNamedPoliciesCtx(ctx, "p", oldPolicies, newPolicies)
}
// UpdateNamedPoliciesCtx updates named policy rules in the storage with context.
func (e *ContextEnforcer) UpdateNamedPoliciesCtx(ctx context.Context, ptype string, p1 [][]string, p2 [][]string) (bool, error) {
return e.updatePoliciesCtx(ctx, "p", ptype, p1, p2)
}
// UpdateFilteredPoliciesCtx updates policy rules that match the filter in the storage with context.
func (e *ContextEnforcer) UpdateFilteredPoliciesCtx(ctx context.Context, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.UpdateFilteredNamedPoliciesCtx(ctx, "p", newPolicies, fieldIndex, fieldValues...)
}
// UpdateFilteredNamedPoliciesCtx updates named policy rules that match the filter in the storage with context.
func (e *ContextEnforcer) UpdateFilteredNamedPoliciesCtx(ctx context.Context, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.updateFilteredPoliciesCtx(ctx, "p", ptype, newPolicies, fieldIndex, fieldValues...)
}
// Grouping Policy Context Methods
// AddGroupingPolicyCtx adds a grouping policy rule to the storage with context.
func (e *ContextEnforcer) AddGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {
return e.AddNamedGroupingPolicyCtx(ctx, "g", params...)
}
// AddGroupingPoliciesCtx adds grouping policy rules to the storage with context.
func (e *ContextEnforcer) AddGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.AddNamedGroupingPoliciesCtx(ctx, "g", rules)
}
func (e *ContextEnforcer) AddGroupingPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.AddNamedGroupingPoliciesExCtx(ctx, "g", rules)
}
// AddNamedGroupingPolicyCtx adds a named grouping policy rule to the storage with context.
func (e *ContextEnforcer) AddNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {
var ruleAdded bool
var err error
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
ruleAdded, err = e.addPolicyCtx(ctx, "g", ptype, strSlice)
} else {
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
ruleAdded, err = e.addPolicyCtx(ctx, "g", ptype, policy)
}
return ruleAdded, err
}
// AddNamedGroupingPoliciesCtx adds named grouping policy rules to the storage with context.
func (e *ContextEnforcer) AddNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesCtx(ctx, "g", ptype, rules, false)
}
func (e *ContextEnforcer) AddNamedGroupingPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesCtx(ctx, "g", ptype, rules, true)
}
// RemoveGroupingPolicyCtx removes a grouping policy rule from the storage with context.
func (e *ContextEnforcer) RemoveGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {
return e.RemoveNamedGroupingPolicyCtx(ctx, "g", params...)
}
// RemoveNamedGroupingPolicyCtx removes a named grouping policy rule from the storage with context.
func (e *ContextEnforcer) RemoveNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {
var ruleRemoved bool
var err error
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
ruleRemoved, err = e.removePolicyCtx(ctx, "g", ptype, strSlice)
} else {
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
ruleRemoved, err = e.removePolicyCtx(ctx, "g", ptype, policy)
}
return ruleRemoved, err
}
// RemoveGroupingPoliciesCtx removes grouping policy rules from the storage with context.
func (e *ContextEnforcer) RemoveGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {
return e.RemoveNamedGroupingPoliciesCtx(ctx, "g", rules)
}
// RemoveNamedGroupingPoliciesCtx removes named grouping policy rules from the storage with context.
func (e *ContextEnforcer) RemoveNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {
return e.removePoliciesCtx(ctx, "g", ptype, rules)
}
// RemoveFilteredGroupingPolicyCtx removes grouping policy rules that match the filter from the storage with context.
func (e *ContextEnforcer) RemoveFilteredGroupingPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error) {
return e.RemoveFilteredNamedGroupingPolicyCtx(ctx, "g", fieldIndex, fieldValues...)
}
// RemoveFilteredNamedGroupingPolicyCtx removes named grouping policy rules that match the filter from the storage with context.
func (e *ContextEnforcer) RemoveFilteredNamedGroupingPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicyCtx(ctx, "g", ptype, fieldIndex, fieldValues)
}
// UpdateGroupingPolicyCtx updates a grouping policy rule in the storage with context.
func (e *ContextEnforcer) UpdateGroupingPolicyCtx(ctx context.Context, oldRule []string, newRule []string) (bool, error) {
return e.UpdateNamedGroupingPolicyCtx(ctx, "g", oldRule, newRule)
}
// UpdateNamedGroupingPolicyCtx updates a named grouping policy rule in the storage with context.
func (e *ContextEnforcer) UpdateNamedGroupingPolicyCtx(ctx context.Context, ptype string, oldRule []string, newRule []string) (bool, error) {
return e.updatePolicyCtx(ctx, "g", ptype, oldRule, newRule)
}
// UpdateGroupingPoliciesCtx updates grouping policy rules in the storage with context.
func (e *ContextEnforcer) UpdateGroupingPoliciesCtx(ctx context.Context, oldRules [][]string, newRules [][]string) (bool, error) {
return e.UpdateNamedGroupingPoliciesCtx(ctx, "g", oldRules, newRules)
}
// UpdateNamedGroupingPoliciesCtx updates named grouping policy rules in the storage with context.
func (e *ContextEnforcer) UpdateNamedGroupingPoliciesCtx(ctx context.Context, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
return e.updatePoliciesCtx(ctx, "g", ptype, oldRules, newRules)
}
// Self Context Methods (bypass watcher notifications)
// SelfAddPolicyCtx adds a policy rule to the current policy with context.
func (e *ContextEnforcer) SelfAddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
return e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
}
// SelfAddPoliciesCtx adds policy rules to the current policy with context.
func (e *ContextEnforcer) SelfAddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, false)
}
func (e *ContextEnforcer) SelfAddPoliciesExCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, true)
}
// SelfRemovePolicyCtx removes a policy rule from the current policy with context.
func (e *ContextEnforcer) SelfRemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
return e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
}
// SelfRemovePoliciesCtx removes policy rules from the current policy with context.
func (e *ContextEnforcer) SelfRemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {
return e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)
}
// SelfRemoveFilteredPolicyCtx removes policy rules that match the filter from the current policy with context.
func (e *ContextEnforcer) SelfRemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype, fieldIndex, fieldValues)
}
// SelfUpdatePolicyCtx updates a policy rule in the current policy with context.
func (e *ContextEnforcer) SelfUpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error) {
return e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule, newRule)
}
// SelfUpdatePoliciesCtx updates policy rules in the current policy with context.
func (e *ContextEnforcer) SelfUpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
return e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules, newRules)
}
// Internal API methods with context support
// addPolicyWithoutNotifyCtx adds a rule to the current policy with context.
func (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})
}
hasPolicy, err := e.model.HasPolicy(sec, ptype, rule)
if hasPolicy || err != nil {
return false, err
}
if e.shouldPersist() {
if err = e.adapterCtx.AddPolicyCtx(ctx, sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
err = e.model.AddPolicy(sec, ptype, rule)
if err != nil {
return false, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})
if err != nil {
return true, err
}
}
return true, nil
}
// addPoliciesWithoutNotifyCtx adds rules to the current policy with context.
func (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
if !autoRemoveRepeat {
hasPolicies, err := e.model.HasPolicies(sec, ptype, rules)
if hasPolicies || err != nil {
return false, err
}
}
if e.shouldPersist() {
if err := e.adapterCtx.(persist.ContextBatchAdapter).AddPoliciesCtx(ctx, sec, ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
err := e.model.AddPolicies(sec, ptype, rules)
if err != nil {
return false, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)
if err != nil {
return true, err
}
err = e.BuildIncrementalConditionalRoleLinks(model.PolicyAdd, ptype, rules)
if err != nil {
return true, err
}
}
return true, nil
}
// removePolicyWithoutNotifyCtx removes a rule from the current policy with context.
func (e *ContextEnforcer) removePolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype, [][]string{rule})
}
if e.shouldPersist() {
if err := e.adapterCtx.RemovePolicyCtx(ctx, sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleRemoved, err := e.model.RemovePolicy(sec, ptype, rule)
if !ruleRemoved || err != nil {
return ruleRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{rule})
if err != nil {
return ruleRemoved, err
}
}
return ruleRemoved, nil
}
// removePoliciesWithoutNotifyCtx removes rules from the current policy with context.
func (e *ContextEnforcer) removePoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules); !hasPolicies || err != nil {
return hasPolicies, err
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype, rules)
}
if e.shouldPersist() {
if err := e.adapterCtx.(persist.ContextBatchAdapter).RemovePoliciesCtx(ctx, sec, ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
rulesRemoved, err := e.model.RemovePolicies(sec, ptype, rules)
if !rulesRemoved || err != nil {
return rulesRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, rules)
if err != nil {
return rulesRemoved, err
}
}
return rulesRemoved, nil
}
// removeFilteredPolicyWithoutNotifyCtx removes policy rules that match the filter from the current policy with context.
func (e *ContextEnforcer) removeFilteredPolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
}
if e.shouldPersist() {
if err := e.adapterCtx.RemoveFilteredPolicyCtx(ctx, sec, ptype, fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleRemoved, effects, err := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
if !ruleRemoved || err != nil {
return ruleRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, effects)
if err != nil {
return ruleRemoved, err
}
}
return ruleRemoved, nil
}
// updatePolicyWithoutNotifyCtx updates a policy rule in the current policy with context.
func (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule, newRule)
}
if e.shouldPersist() {
if err := e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePolicyCtx(ctx, sec, ptype, oldRule, newRule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleUpdated, err := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule
if err != nil {
return ruleUpdated, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule
if err != nil {
return ruleUpdated, err
}
}
return ruleUpdated, nil
}
func (e *ContextEnforcer) updatePoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
if len(newRules) != len(oldRules) {
return false, fmt.Errorf("the length of oldRules should be equal to the length of newRules, but got the length of oldRules is %d, the length of newRules is %d", len(oldRules), len(newRules))
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules, newRules)
}
if e.shouldPersist() {
if err := e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePoliciesCtx(ctx, sec, ptype, oldRules, newRules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleUpdated, err := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
if err != nil {
return ruleUpdated, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
if err != nil {
return ruleUpdated, err
}
}
return ruleUpdated, nil
}
func (e *ContextEnforcer) addPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
ok, err := e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForAddPolicy(sec, ptype, rule...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) addPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
ok, err := e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, autoRemoveRepeat)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForAddPolicies(sec, ptype, rules...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) updatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
ok, err := e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule, newRule)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicy(sec, ptype, oldRule, newRule)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) updatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
ok, err := e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules, newRules)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) removePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {
ok, err := e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForRemovePolicy(sec, ptype, rule...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) removePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {
ok, err := e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForRemovePolicies(sec, ptype, rules...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
// removeFilteredPolicy removes rules based on field filters from the current policy.
func (e *ContextEnforcer) removeFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {
ok, err := e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype, fieldIndex, fieldValues)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) updateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
oldRules, err := e.updateFilteredPoliciesWithoutNotifyCtx(ctx, sec, ptype, newRules, fieldIndex, fieldValues...)
ok := len(oldRules) != 0
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *ContextEnforcer) updateFilteredPoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
var (
oldRules [][]string
err error
)
if _, err = e.model.GetAssertion(sec, ptype); err != nil {
return oldRules, err
}
if e.shouldPersist() {
if oldRules, err = e.adapter.(persist.ContextUpdatableAdapter).UpdateFilteredPoliciesCtx(ctx, sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return nil, err
}
}
// For compatibility, because some adapters return oldRules containing ptype, see https://github.com/casbin/xorm-adapter/issues/49
for i, oldRule := range oldRules {
if len(oldRules[i]) == len(e.model[sec][ptype].Tokens)+1 {
oldRules[i] = oldRule[1:]
}
}
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return oldRules, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)
}
ruleChanged, err := e.model.RemovePolicies(sec, ptype, oldRules)
if err != nil {
return oldRules, err
}
err = e.model.AddPolicies(sec, ptype, newRules)
if err != nil {
return oldRules, err
}
ruleChanged = ruleChanged && len(newRules) != 0
if !ruleChanged {
return make([][]string, 0), nil
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
if err != nil {
return oldRules, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
if err != nil {
return oldRules, err
}
}
return oldRules, nil
}
================================================
FILE: enforcer_context_interface.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import "context"
type IEnforcerContext interface {
IEnforcer
/* Enforcer API */
LoadPolicyCtx(ctx context.Context) error
LoadFilteredPolicyCtx(ctx context.Context, filter interface{}) error
LoadIncrementalFilteredPolicyCtx(ctx context.Context, filter interface{}) error
IsFilteredCtx(ctx context.Context) bool
SavePolicyCtx(ctx context.Context) error
/* RBAC API */
AddRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error)
AddPermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error)
AddPermissionsForUserCtx(ctx context.Context, user string, permissions ...[]string) (bool, error)
DeletePermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error)
DeletePermissionsForUserCtx(ctx context.Context, user string) (bool, error)
DeleteRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error)
DeleteRolesForUserCtx(ctx context.Context, user string, domain ...string) (bool, error)
DeleteUserCtx(ctx context.Context, user string) (bool, error)
DeleteRoleCtx(ctx context.Context, role string) (bool, error)
DeletePermissionCtx(ctx context.Context, permission ...string) (bool, error)
/* RBAC API with domains*/
AddRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error)
DeleteRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error)
DeleteRolesForUserInDomainCtx(ctx context.Context, user string, domain string) (bool, error)
DeleteAllUsersByDomainCtx(ctx context.Context, domain string) (bool, error)
DeleteDomainsCtx(ctx context.Context, domains ...string) (bool, error)
/* Management API */
AddPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)
AddPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)
AddNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)
AddNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
AddPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error)
AddNamedPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
RemovePolicyCtx(ctx context.Context, params ...interface{}) (bool, error)
RemovePoliciesCtx(ctx context.Context, rules [][]string) (bool, error)
RemoveFilteredPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error)
RemoveNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)
RemoveNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
RemoveFilteredNamedPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error)
AddGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)
AddGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)
AddGroupingPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error)
AddNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)
AddNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
AddNamedGroupingPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
RemoveGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)
RemoveGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)
RemoveFilteredGroupingPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error)
RemoveNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)
RemoveNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)
RemoveFilteredNamedGroupingPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error)
UpdatePolicyCtx(ctx context.Context, oldPolicy []string, newPolicy []string) (bool, error)
UpdatePoliciesCtx(ctx context.Context, oldPolicies [][]string, newPolicies [][]string) (bool, error)
UpdateFilteredPoliciesCtx(ctx context.Context, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error)
UpdateGroupingPolicyCtx(ctx context.Context, oldRule []string, newRule []string) (bool, error)
UpdateGroupingPoliciesCtx(ctx context.Context, oldRules [][]string, newRules [][]string) (bool, error)
UpdateNamedGroupingPolicyCtx(ctx context.Context, ptype string, oldRule []string, newRule []string) (bool, error)
UpdateNamedGroupingPoliciesCtx(ctx context.Context, ptype string, oldRules [][]string, newRules [][]string) (bool, error)
/* Management API with autoNotifyWatcher disabled */
SelfAddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error)
SelfAddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)
SelfAddPoliciesExCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)
SelfRemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error)
SelfRemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)
SelfRemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error)
SelfUpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error)
SelfUpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) (bool, error)
}
================================================
FILE: enforcer_context_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"testing"
"time"
)
func TestIEnforcerContext_BasicOperations(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
err = e.LoadPolicyCtx(ctx)
if err != nil {
t.Fatalf("LoadPolicyCtx failed: %v", err)
}
added, err := e.AddPolicyCtx(ctx, "eve", "data3", "read")
if err != nil {
t.Fatalf("AddPolicyCtx failed: %v", err)
}
if !added {
t.Error("AddPolicyCtx should return true for new policy")
}
removed, err := e.RemovePolicyCtx(ctx, "eve", "data3", "read")
if err != nil {
t.Fatalf("RemovePolicyCtx failed: %v", err)
}
if !removed {
t.Error("RemovePolicyCtx should return true for existing policy")
}
err = e.SavePolicyCtx(ctx)
if err != nil {
t.Fatalf("SavePolicyCtx failed: %v", err)
}
}
func TestIEnforcerContext_RBACOperations(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
added, err := e.AddRoleForUserCtx(ctx, "eve", "data1_admin")
if err != nil {
t.Fatalf("AddRoleForUserCtx failed: %v", err)
}
if !added {
t.Error("AddRoleForUserCtx should return true for new role")
}
added, err = e.AddPermissionForUserCtx(ctx, "eve", "data3", "write")
if err != nil {
t.Fatalf("AddPermissionForUserCtx failed: %v", err)
}
if !added {
t.Error("AddPermissionForUserCtx should return true for new permission")
}
deleted, err := e.DeleteRoleForUserCtx(ctx, "eve", "data1_admin")
if err != nil {
t.Fatalf("DeleteRoleForUserCtx failed: %v", err)
}
if !deleted {
t.Error("DeleteRoleForUserCtx should return true for existing role")
}
deleted, err = e.DeletePermissionForUserCtx(ctx, "eve", "data3", "write")
if err != nil {
t.Fatalf("DeletePermissionForUserCtx failed: %v", err)
}
if !deleted {
t.Error("DeletePermissionForUserCtx should return true for existing permission")
}
}
func TestIEnforcerContext_BatchOperations(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
rules := [][]string{
{"eve", "data3", "read"},
{"eve", "data3", "write"},
}
added, err := e.AddPoliciesCtx(ctx, rules)
if err != nil {
t.Fatalf("AddPoliciesCtx failed: %v", err)
}
if !added {
t.Error("AddPoliciesCtx should return true for new policies")
}
removed, err := e.RemovePoliciesCtx(ctx, rules)
if err != nil {
t.Fatalf("RemovePoliciesCtx failed: %v", err)
}
if !removed {
t.Error("RemovePoliciesCtx should return true for existing policies")
}
}
func TestIEnforcerContext_ContextCancellation(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
err = e.LoadPolicyCtx(ctx)
if err != nil {
t.Logf("LoadPolicyCtx with cancelled context returned error: %v", err)
}
}
func TestIEnforcerContext_ContextTimeout(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()
time.Sleep(2 * time.Millisecond)
_, err = e.AddPolicyCtx(ctx, "test", "data", "read")
if err != nil {
t.Logf("AddPolicyCtx with timeout context returned error: %v", err)
}
}
func TestIEnforcerContext_GroupingPolicyOperations(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
added, err := e.AddGroupingPolicyCtx(ctx, "eve", "data3_admin")
if err != nil {
t.Fatalf("AddGroupingPolicyCtx failed: %v", err)
}
if !added {
t.Error("AddGroupingPolicyCtx should return true for new grouping policy")
}
removed, err := e.RemoveGroupingPolicyCtx(ctx, "eve", "data3_admin")
if err != nil {
t.Fatalf("RemoveGroupingPolicyCtx failed: %v", err)
}
if !removed {
t.Error("RemoveGroupingPolicyCtx should return true for existing grouping policy")
}
}
func TestIEnforcerContext_UpdateOperations(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
_, err = e.AddPolicyCtx(ctx, "eve", "data3", "read")
if err != nil {
t.Fatalf("AddPolicyCtx failed: %v", err)
}
updated, err := e.UpdatePolicyCtx(ctx, []string{"eve", "data3", "read"}, []string{"eve", "data3", "write"})
if err != nil {
t.Fatalf("UpdatePolicyCtx failed: %v", err)
}
if !updated {
t.Error("UpdatePolicyCtx should return true for successful update")
}
_, _ = e.RemovePolicyCtx(ctx, "eve", "data3", "write")
}
func TestIEnforcerContext_SelfMethods(t *testing.T) {
e, err := NewContextEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("NewContextEnforcer failed: %v", err)
}
ctx := context.Background()
added, err := e.SelfAddPolicyCtx(ctx, "p", "p", []string{"eve", "data3", "read"})
if err != nil {
t.Fatalf("SelfAddPolicyCtx failed: %v", err)
}
if !added {
t.Error("SelfAddPolicyCtx should return true for new policy")
}
removed, err := e.SelfRemovePolicyCtx(ctx, "p", "p", []string{"eve", "data3", "read"})
if err != nil {
t.Fatalf("SelfRemovePolicyCtx failed: %v", err)
}
if !removed {
t.Error("SelfRemovePolicyCtx should return true for existing policy")
}
}
================================================
FILE: enforcer_distributed.go
================================================
package casbin
import (
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// DistributedEnforcer wraps SyncedEnforcer for dispatcher.
type DistributedEnforcer struct {
*SyncedEnforcer
}
func NewDistributedEnforcer(params ...interface{}) (*DistributedEnforcer, error) {
e := &DistributedEnforcer{}
var err error
e.SyncedEnforcer, err = NewSyncedEnforcer(params...)
if err != nil {
return nil, err
}
return e, nil
}
// SetDispatcher sets the current dispatcher.
func (d *DistributedEnforcer) SetDispatcher(dispatcher persist.Dispatcher) {
d.dispatcher = dispatcher
}
// AddPoliciesSelf provides a method for dispatcher to add authorization rules to the current policy.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) AddPoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error) {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
var noExistsPolicy [][]string
for _, rule := range rules {
var hasPolicy bool
hasPolicy, err = d.model.HasPolicy(sec, ptype, rule)
if err != nil {
return nil, err
}
if !hasPolicy {
noExistsPolicy = append(noExistsPolicy, rule)
}
}
if err = d.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, noExistsPolicy); err != nil && err.Error() != notImplemented {
return nil, err
}
}
affected, err = d.model.AddPoliciesWithAffected(sec, ptype, rules)
if err != nil {
return affected, err
}
if sec == "g" {
err := d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, affected)
if err != nil {
return affected, err
}
}
return affected, nil
}
// RemovePoliciesSelf provides a method for dispatcher to remove a set of rules from current policy.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) RemovePoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error) {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
if err = d.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {
if err.Error() != notImplemented {
return nil, err
}
}
}
affected, err = d.model.RemovePoliciesWithAffected(sec, ptype, rules)
if err != nil {
return affected, err
}
if sec == "g" {
err = d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)
if err != nil {
return affected, err
}
}
return affected, err
}
// RemoveFilteredPolicySelf provides a method for dispatcher to remove an authorization rule from the current policy, field filters can be specified.
// The function returns the rules affected and error.
func (d *DistributedEnforcer) RemoveFilteredPolicySelf(shouldPersist func() bool, sec string, ptype string, fieldIndex int, fieldValues ...string) (affected [][]string, err error) {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
if err = d.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return nil, err
}
}
}
_, affected, err = d.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
if err != nil {
return affected, err
}
if sec == "g" {
err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)
if err != nil {
return affected, err
}
}
return affected, nil
}
// ClearPolicySelf provides a method for dispatcher to clear all rules from the current policy.
func (d *DistributedEnforcer) ClearPolicySelf(shouldPersist func() bool) error {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
err := d.adapter.SavePolicy(nil)
if err != nil {
return err
}
}
d.model.ClearPolicy()
return nil
}
// UpdatePolicySelf provides a method for dispatcher to update an authorization rule from the current policy.
func (d *DistributedEnforcer) UpdatePolicySelf(shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string) (affected bool, err error) {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
err = d.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule)
if err != nil {
return false, err
}
}
ruleUpdated, err := d.model.UpdatePolicy(sec, ptype, oldRule, newRule)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule
if err != nil {
return ruleUpdated, err
}
err = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule
if err != nil {
return ruleUpdated, err
}
}
return ruleUpdated, nil
}
// UpdatePoliciesSelf provides a method for dispatcher to update a set of authorization rules from the current policy.
func (d *DistributedEnforcer) UpdatePoliciesSelf(shouldPersist func() bool, sec string, ptype string, oldRules, newRules [][]string) (affected bool, err error) {
d.m.Lock()
defer d.m.Unlock()
if shouldPersist != nil && shouldPersist() {
err = d.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules)
if err != nil {
return false, err
}
}
ruleUpdated, err := d.model.UpdatePolicies(sec, ptype, oldRules, newRules)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rule
if err != nil {
return ruleUpdated, err
}
err = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rule
if err != nil {
return ruleUpdated, err
}
}
return ruleUpdated, nil
}
// UpdateFilteredPoliciesSelf provides a method for dispatcher to update a set of authorization rules from the current policy.
func (d *DistributedEnforcer) UpdateFilteredPoliciesSelf(shouldPersist func() bool, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
d.m.Lock()
defer d.m.Unlock()
var (
oldRules [][]string
err error
)
if shouldPersist != nil && shouldPersist() {
oldRules, err = d.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...)
if err != nil {
return false, err
}
}
ruleChanged, err := d.model.RemovePolicies(sec, ptype, oldRules)
if err != nil {
return ruleChanged, err
}
err = d.model.AddPolicies(sec, ptype, newRules)
if err != nil {
return ruleChanged, err
}
ruleChanged = ruleChanged && len(newRules) != 0
if !ruleChanged {
return ruleChanged, nil
}
if sec == "g" {
err := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rule
if err != nil {
return ruleChanged, err
}
err = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rule
if err != nil {
return ruleChanged, err
}
}
return true, nil
}
================================================
FILE: enforcer_interface.go
================================================
// Copyright 2019 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"github.com/casbin/casbin/v3/effector"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/govaluate"
)
var _ IEnforcer = &Enforcer{}
var _ IEnforcer = &SyncedEnforcer{}
var _ IEnforcer = &CachedEnforcer{}
// IEnforcer is the API interface of Enforcer.
type IEnforcer interface {
/* Enforcer API */
InitWithFile(modelPath string, policyPath string) error
InitWithAdapter(modelPath string, adapter persist.Adapter) error
InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error
LoadModel() error
GetModel() model.Model
SetModel(m model.Model)
GetAdapter() persist.Adapter
SetAdapter(adapter persist.Adapter)
SetWatcher(watcher persist.Watcher) error
GetRoleManager() rbac.RoleManager
SetRoleManager(rm rbac.RoleManager)
SetEffector(eft effector.Effector)
SetAIConfig(config AIConfig)
ClearPolicy()
LoadPolicy() error
LoadFilteredPolicy(filter interface{}) error
LoadIncrementalFilteredPolicy(filter interface{}) error
IsFiltered() bool
SavePolicy() error
EnableEnforce(enable bool)
EnableAutoNotifyWatcher(enable bool)
EnableAutoSave(autoSave bool)
EnableAutoBuildRoleLinks(autoBuildRoleLinks bool)
BuildRoleLinks() error
Enforce(rvals ...interface{}) (bool, error)
EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error)
EnforceEx(rvals ...interface{}) (bool, []string, error)
EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error)
BatchEnforce(requests [][]interface{}) ([]bool, error)
BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error)
Explain(rvals ...interface{}) (string, error)
/* RBAC API */
GetRolesForUser(name string, domain ...string) ([]string, error)
GetUsersForRole(name string, domain ...string) ([]string, error)
HasRoleForUser(name string, role string, domain ...string) (bool, error)
AddRoleForUser(user string, role string, domain ...string) (bool, error)
AddPermissionForUser(user string, permission ...string) (bool, error)
AddPermissionsForUser(user string, permissions ...[]string) (bool, error)
DeletePermissionForUser(user string, permission ...string) (bool, error)
DeletePermissionsForUser(user string) (bool, error)
GetPermissionsForUser(user string, domain ...string) ([][]string, error)
HasPermissionForUser(user string, permission ...string) (bool, error)
GetImplicitRolesForUser(name string, domain ...string) ([]string, error)
GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error)
GetImplicitUsersForPermission(permission ...string) ([]string, error)
DeleteRoleForUser(user string, role string, domain ...string) (bool, error)
DeleteRolesForUser(user string, domain ...string) (bool, error)
DeleteUser(user string) (bool, error)
DeleteRole(role string) (bool, error)
DeletePermission(permission ...string) (bool, error)
/* RBAC API with domains*/
GetUsersForRoleInDomain(name string, domain string) []string
GetRolesForUserInDomain(name string, domain string) []string
GetPermissionsForUserInDomain(user string, domain string) [][]string
AddRoleForUserInDomain(user string, role string, domain string) (bool, error)
DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error)
GetAllUsersByDomain(domain string) ([]string, error)
DeleteRolesForUserInDomain(user string, domain string) (bool, error)
DeleteAllUsersByDomain(domain string) (bool, error)
DeleteDomains(domains ...string) (bool, error)
GetAllDomains() ([]string, error)
GetAllRolesByDomain(domain string) ([]string, error)
/* Management API */
GetAllSubjects() ([]string, error)
GetAllNamedSubjects(ptype string) ([]string, error)
GetAllObjects() ([]string, error)
GetAllNamedObjects(ptype string) ([]string, error)
GetAllActions() ([]string, error)
GetAllNamedActions(ptype string) ([]string, error)
GetAllRoles() ([]string, error)
GetAllNamedRoles(ptype string) ([]string, error)
GetAllUsers() ([]string, error)
GetPolicy() ([][]string, error)
GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)
GetNamedPolicy(ptype string) ([][]string, error)
GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)
GetGroupingPolicy() ([][]string, error)
GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)
GetNamedGroupingPolicy(ptype string) ([][]string, error)
GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)
HasPolicy(params ...interface{}) (bool, error)
HasNamedPolicy(ptype string, params ...interface{}) (bool, error)
AddPolicy(params ...interface{}) (bool, error)
AddPolicies(rules [][]string) (bool, error)
AddNamedPolicy(ptype string, params ...interface{}) (bool, error)
AddNamedPolicies(ptype string, rules [][]string) (bool, error)
AddPoliciesEx(rules [][]string) (bool, error)
AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error)
RemovePolicy(params ...interface{}) (bool, error)
RemovePolicies(rules [][]string) (bool, error)
RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error)
RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error)
RemoveNamedPolicies(ptype string, rules [][]string) (bool, error)
RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error)
HasGroupingPolicy(params ...interface{}) (bool, error)
HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)
AddGroupingPolicy(params ...interface{}) (bool, error)
AddGroupingPolicies(rules [][]string) (bool, error)
AddGroupingPoliciesEx(rules [][]string) (bool, error)
AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)
AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error)
AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error)
RemoveGroupingPolicy(params ...interface{}) (bool, error)
RemoveGroupingPolicies(rules [][]string) (bool, error)
RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error)
RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)
RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error)
RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error)
AddFunction(name string, function govaluate.ExpressionFunction)
UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error)
UpdatePolicies(oldPolicies [][]string, newPolicies [][]string) (bool, error)
UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error)
UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error)
UpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error)
UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error)
UpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error)
/* Management API with autoNotifyWatcher disabled */
SelfAddPolicy(sec string, ptype string, rule []string) (bool, error)
SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error)
SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error)
SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error)
SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error)
SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error)
SelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error)
SelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error)
}
var _ IDistributedEnforcer = &DistributedEnforcer{}
// IDistributedEnforcer defines dispatcher enforcer.
type IDistributedEnforcer interface {
IEnforcer
SetDispatcher(dispatcher persist.Dispatcher)
/* Management API for DistributedEnforcer*/
AddPoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error)
RemovePoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error)
RemoveFilteredPolicySelf(shouldPersist func() bool, sec string, ptype string, fieldIndex int, fieldValues ...string) (affected [][]string, err error)
ClearPolicySelf(shouldPersist func() bool) error
UpdatePolicySelf(shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string) (affected bool, err error)
UpdatePoliciesSelf(shouldPersist func() bool, sec string, ptype string, oldRules, newRules [][]string) (affected bool, err error)
UpdateFilteredPoliciesSelf(shouldPersist func() bool, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error)
}
================================================
FILE: enforcer_json_test.go
================================================
package casbin
import (
"strings"
"testing"
"github.com/casbin/casbin/v3/model"
)
func TestInvalidJsonRequest(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub.Name == " "
`
m, err := model.NewModelFromString(modelText)
if err != nil {
t.Fatalf("Failed to create model: %v", err)
}
e, err := NewEnforcer(m)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
e.EnableAcceptJsonRequest(true)
// Test with invalid JSON (contains \x escape sequence which is not valid in JSON)
invalidJSON := `{"Name": "\x20"}`
_, err = e.Enforce(invalidJSON, "obj", "read")
if err == nil {
t.Fatalf("Expected error for invalid JSON, got nil")
}
if !strings.Contains(err.Error(), "failed to parse JSON parameter") {
t.Fatalf("Expected error message to contain 'failed to parse JSON parameter', got: %v", err)
}
// Test with valid JSON - should work
validJSON := `{"Name": " "}`
res, err := e.Enforce(validJSON, "obj", "read")
if err != nil {
t.Fatalf("Valid JSON should not return error: %v", err)
}
if !res {
t.Fatalf("Expected true for valid JSON with matching Name")
}
// Test with plain string (doesn't start with { or [) - should not try to parse as JSON
plainString := "alice"
_, err = e.Enforce(plainString, "obj", "read")
// This will fail because plainString is not a struct with Name field,
// but it shouldn't fail with JSON parsing error
if err != nil && strings.Contains(err.Error(), "failed to parse JSON parameter") {
t.Fatalf("Plain string should not trigger JSON parsing error: %v", err)
}
}
================================================
FILE: enforcer_synced.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sync"
"sync/atomic"
"time"
"github.com/casbin/govaluate"
"github.com/casbin/casbin/v3/persist"
"github.com/casbin/casbin/v3/rbac"
)
// SyncedEnforcer wraps Enforcer and provides synchronized access.
type SyncedEnforcer struct {
*Enforcer
m sync.RWMutex
stopAutoLoad chan struct{}
autoLoadRunning int32
}
// NewSyncedEnforcer creates a synchronized enforcer via file or DB.
func NewSyncedEnforcer(params ...interface{}) (*SyncedEnforcer, error) {
e := &SyncedEnforcer{}
var err error
e.Enforcer, err = NewEnforcer(params...)
if err != nil {
return nil, err
}
e.stopAutoLoad = make(chan struct{}, 1)
e.autoLoadRunning = 0
return e, nil
}
// GetLock return the private RWMutex lock.
func (e *SyncedEnforcer) GetLock() *sync.RWMutex {
return &e.m
}
// GetRoleManager gets the current role manager with synchronization.
func (e *SyncedEnforcer) GetRoleManager() rbac.RoleManager {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetRoleManager()
}
// GetNamedRoleManager gets the role manager for the named policy with synchronization.
func (e *SyncedEnforcer) GetNamedRoleManager(ptype string) rbac.RoleManager {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetNamedRoleManager(ptype)
}
// SetRoleManager sets the current role manager with synchronization.
func (e *SyncedEnforcer) SetRoleManager(rm rbac.RoleManager) {
e.m.Lock()
defer e.m.Unlock()
e.Enforcer.SetRoleManager(rm)
}
// SetNamedRoleManager sets the role manager for the named policy with synchronization.
func (e *SyncedEnforcer) SetNamedRoleManager(ptype string, rm rbac.RoleManager) {
e.m.Lock()
defer e.m.Unlock()
e.Enforcer.SetNamedRoleManager(ptype, rm)
}
// IsAutoLoadingRunning check if SyncedEnforcer is auto loading policies.
func (e *SyncedEnforcer) IsAutoLoadingRunning() bool {
return atomic.LoadInt32(&(e.autoLoadRunning)) != 0
}
// StartAutoLoadPolicy starts a go routine that will every specified duration call LoadPolicy.
func (e *SyncedEnforcer) StartAutoLoadPolicy(d time.Duration) {
// Don't start another goroutine if there is already one running
if !atomic.CompareAndSwapInt32(&e.autoLoadRunning, 0, 1) {
return
}
ticker := time.NewTicker(d)
go func() {
defer func() {
ticker.Stop()
atomic.StoreInt32(&(e.autoLoadRunning), int32(0))
}()
n := 1
for {
select {
case <-ticker.C:
// error intentionally ignored
_ = e.LoadPolicy()
// Uncomment this line to see when the policy is loaded.
// log.Print("Load policy for time: ", n)
n++
case <-e.stopAutoLoad:
return
}
}
}()
}
// StopAutoLoadPolicy causes the go routine to exit.
func (e *SyncedEnforcer) StopAutoLoadPolicy() {
if e.IsAutoLoadingRunning() {
e.stopAutoLoad <- struct{}{}
}
}
// SetWatcher sets the current watcher.
func (e *SyncedEnforcer) SetWatcher(watcher persist.Watcher) error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SetWatcher(watcher)
}
// LoadModel reloads the model from the model CONF file.
func (e *SyncedEnforcer) LoadModel() error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.LoadModel()
}
// ClearPolicy clears all policy.
func (e *SyncedEnforcer) ClearPolicy() {
e.m.Lock()
defer e.m.Unlock()
e.Enforcer.ClearPolicy()
}
// LoadPolicy reloads the policy from file/database.
func (e *SyncedEnforcer) LoadPolicy() error {
e.m.RLock()
newModel, err := e.loadPolicyFromAdapter(e.model)
e.m.RUnlock()
if err != nil {
return err
}
e.m.Lock()
err = e.applyModifiedModel(newModel)
e.m.Unlock()
if err != nil {
return err
}
return nil
}
// LoadFilteredPolicy reloads a filtered policy from file/database.
func (e *SyncedEnforcer) LoadFilteredPolicy(filter interface{}) error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.LoadFilteredPolicy(filter)
}
// LoadIncrementalFilteredPolicy reloads a filtered policy from file/database.
func (e *SyncedEnforcer) LoadIncrementalFilteredPolicy(filter interface{}) error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.LoadIncrementalFilteredPolicy(filter)
}
// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database.
func (e *SyncedEnforcer) SavePolicy() error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SavePolicy()
}
// BuildRoleLinks manually rebuild the role inheritance relations.
func (e *SyncedEnforcer) BuildRoleLinks() error {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.BuildRoleLinks()
}
// Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
func (e *SyncedEnforcer) Enforce(rvals ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.Enforce(rvals...)
}
// EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
func (e *SyncedEnforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.EnforceWithMatcher(matcher, rvals...)
}
// EnforceEx explain enforcement by informing matched rules.
func (e *SyncedEnforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.EnforceEx(rvals...)
}
// EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules.
func (e *SyncedEnforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.EnforceExWithMatcher(matcher, rvals...)
}
// BatchEnforce enforce in batches.
func (e *SyncedEnforcer) BatchEnforce(requests [][]interface{}) ([]bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.BatchEnforce(requests)
}
// BatchEnforceWithMatcher enforce with matcher in batches.
func (e *SyncedEnforcer) BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.BatchEnforceWithMatcher(matcher, requests)
}
// GetAllSubjects gets the list of subjects that show up in the current policy.
func (e *SyncedEnforcer) GetAllSubjects() ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllSubjects()
}
// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.
func (e *SyncedEnforcer) GetAllNamedSubjects(ptype string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllNamedSubjects(ptype)
}
// GetAllObjects gets the list of objects that show up in the current policy.
func (e *SyncedEnforcer) GetAllObjects() ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllObjects()
}
// GetAllNamedObjects gets the list of objects that show up in the current named policy.
func (e *SyncedEnforcer) GetAllNamedObjects(ptype string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllNamedObjects(ptype)
}
// GetAllActions gets the list of actions that show up in the current policy.
func (e *SyncedEnforcer) GetAllActions() ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllActions()
}
// GetAllNamedActions gets the list of actions that show up in the current named policy.
func (e *SyncedEnforcer) GetAllNamedActions(ptype string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllNamedActions(ptype)
}
// GetAllRoles gets the list of roles that show up in the current policy.
func (e *SyncedEnforcer) GetAllRoles() ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllRoles()
}
// GetAllNamedRoles gets the list of roles that show up in the current named policy.
func (e *SyncedEnforcer) GetAllNamedRoles(ptype string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllNamedRoles(ptype)
}
// GetAllUsers gets the list of users that show up in the current policy.
func (e *SyncedEnforcer) GetAllUsers() ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetAllUsers()
}
// GetPolicy gets all the authorization rules in the policy.
func (e *SyncedEnforcer) GetPolicy() ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetPolicy()
}
// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.
func (e *SyncedEnforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetFilteredPolicy(fieldIndex, fieldValues...)
}
// GetNamedPolicy gets all the authorization rules in the named policy.
func (e *SyncedEnforcer) GetNamedPolicy(ptype string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetNamedPolicy(ptype)
}
// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.
func (e *SyncedEnforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetFilteredNamedPolicy(ptype, fieldIndex, fieldValues...)
}
// GetGroupingPolicy gets all the role inheritance rules in the policy.
func (e *SyncedEnforcer) GetGroupingPolicy() ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetGroupingPolicy()
}
// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
func (e *SyncedEnforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetFilteredGroupingPolicy(fieldIndex, fieldValues...)
}
// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.
func (e *SyncedEnforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetNamedGroupingPolicy(ptype)
}
// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
func (e *SyncedEnforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...)
}
// HasPolicy determines whether an authorization rule exists.
func (e *SyncedEnforcer) HasPolicy(params ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasPolicy(params...)
}
// HasNamedPolicy determines whether a named authorization rule exists.
func (e *SyncedEnforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasNamedPolicy(ptype, params...)
}
// AddPolicy adds an authorization rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *SyncedEnforcer) AddPolicy(params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddPolicy(params...)
}
// AddPolicies adds authorization rules to the current policy.
// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
// Otherwise the function returns true for the corresponding rule by adding the new rule.
func (e *SyncedEnforcer) AddPolicies(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddPolicies(rules)
}
// AddPoliciesEx adds authorization rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddPolicies, other non-existent rules are added instead of returning false directly.
func (e *SyncedEnforcer) AddPoliciesEx(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddPoliciesEx(rules)
}
// AddNamedPolicy adds an authorization rule to the current named policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *SyncedEnforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedPolicy(ptype, params...)
}
// AddNamedPolicies adds authorization rules to the current named policy.
// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
// Otherwise the function returns true for the corresponding by adding the new rule.
func (e *SyncedEnforcer) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedPolicies(ptype, rules)
}
// AddNamedPoliciesEx adds authorization rules to the current named policy.
// If the rule already exists, the rule will not be added.
// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly.
func (e *SyncedEnforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedPoliciesEx(ptype, rules)
}
// RemovePolicy removes an authorization rule from the current policy.
func (e *SyncedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemovePolicy(params...)
}
// UpdatePolicy updates an authorization rule from the current policy.
func (e *SyncedEnforcer) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdatePolicy(oldPolicy, newPolicy)
}
func (e *SyncedEnforcer) UpdateNamedPolicy(ptype string, p1 []string, p2 []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateNamedPolicy(ptype, p1, p2)
}
// UpdatePolicies updates authorization rules from the current policies.
func (e *SyncedEnforcer) UpdatePolicies(oldPolices [][]string, newPolicies [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdatePolicies(oldPolices, newPolicies)
}
func (e *SyncedEnforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateNamedPolicies(ptype, p1, p2)
}
func (e *SyncedEnforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateFilteredPolicies(newPolicies, fieldIndex, fieldValues...)
}
func (e *SyncedEnforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateFilteredNamedPolicies(ptype, newPolicies, fieldIndex, fieldValues...)
}
// RemovePolicies removes authorization rules from the current policy.
func (e *SyncedEnforcer) RemovePolicies(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemovePolicies(rules)
}
// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified.
func (e *SyncedEnforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveFilteredPolicy(fieldIndex, fieldValues...)
}
// RemoveNamedPolicy removes an authorization rule from the current named policy.
func (e *SyncedEnforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveNamedPolicy(ptype, params...)
}
// RemoveNamedPolicies removes authorization rules from the current named policy.
func (e *SyncedEnforcer) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveNamedPolicies(ptype, rules)
}
// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified.
func (e *SyncedEnforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveFilteredNamedPolicy(ptype, fieldIndex, fieldValues...)
}
// HasGroupingPolicy determines whether a role inheritance rule exists.
func (e *SyncedEnforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasGroupingPolicy(params...)
}
// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.
func (e *SyncedEnforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasNamedGroupingPolicy(ptype, params...)
}
// AddGroupingPolicy adds a role inheritance rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *SyncedEnforcer) AddGroupingPolicy(params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddGroupingPolicy(params...)
}
// AddGroupingPolicies adds role inheritance rulea to the current policy.
// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
// Otherwise the function returns true for the corresponding policy rule by adding the new rule.
func (e *SyncedEnforcer) AddGroupingPolicies(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddGroupingPolicies(rules)
}
// AddGroupingPoliciesEx adds role inheritance rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly.
func (e *SyncedEnforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddGroupingPoliciesEx(rules)
}
// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *SyncedEnforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedGroupingPolicy(ptype, params...)
}
// AddNamedGroupingPolicies adds named role inheritance rules to the current policy.
// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
// Otherwise the function returns true for the corresponding policy rule by adding the new rule.
func (e *SyncedEnforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedGroupingPolicies(ptype, rules)
}
// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly.
func (e *SyncedEnforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddNamedGroupingPoliciesEx(ptype, rules)
}
// RemoveGroupingPolicy removes a role inheritance rule from the current policy.
func (e *SyncedEnforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveGroupingPolicy(params...)
}
// RemoveGroupingPolicies removes role inheritance rules from the current policy.
func (e *SyncedEnforcer) RemoveGroupingPolicies(rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveGroupingPolicies(rules)
}
// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified.
func (e *SyncedEnforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveFilteredGroupingPolicy(fieldIndex, fieldValues...)
}
// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy.
func (e *SyncedEnforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveNamedGroupingPolicy(ptype, params...)
}
// RemoveNamedGroupingPolicies removes role inheritance rules from the current named policy.
func (e *SyncedEnforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveNamedGroupingPolicies(ptype, rules)
}
func (e *SyncedEnforcer) UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateGroupingPolicy(oldRule, newRule)
}
func (e *SyncedEnforcer) UpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateGroupingPolicies(oldRules, newRules)
}
func (e *SyncedEnforcer) UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateNamedGroupingPolicy(ptype, oldRule, newRule)
}
func (e *SyncedEnforcer) UpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.UpdateNamedGroupingPolicies(ptype, oldRules, newRules)
}
// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified.
func (e *SyncedEnforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.RemoveFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...)
}
// AddFunction adds a customized function.
func (e *SyncedEnforcer) AddFunction(name string, function govaluate.ExpressionFunction) {
e.m.Lock()
defer e.m.Unlock()
e.Enforcer.AddFunction(name, function)
}
func (e *SyncedEnforcer) SelfAddPolicy(sec string, ptype string, rule []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfAddPolicy(sec, ptype, rule)
}
func (e *SyncedEnforcer) SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfAddPolicies(sec, ptype, rules)
}
func (e *SyncedEnforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfAddPoliciesEx(sec, ptype, rules)
}
func (e *SyncedEnforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfRemovePolicy(sec, ptype, rule)
}
func (e *SyncedEnforcer) SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfRemovePolicies(sec, ptype, rules)
}
func (e *SyncedEnforcer) SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
}
func (e *SyncedEnforcer) SelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfUpdatePolicy(sec, ptype, oldRule, newRule)
}
func (e *SyncedEnforcer) SelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.SelfUpdatePolicies(sec, ptype, oldRules, newRules)
}
================================================
FILE: enforcer_synced_test.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sort"
"testing"
"time"
"github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/util"
)
func testEnforceSync(t *testing.T, e *SyncedEnforcer, sub string, obj interface{}, act string, res bool) {
t.Helper()
if myRes, _ := e.Enforce(sub, obj, act); myRes != res {
t.Errorf("%s, %v, %s: %t, supposed to be %t", sub, obj, act, myRes, res)
}
}
func TestSync(t *testing.T) {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
// Start reloading the policy every 200 ms.
e.StartAutoLoadPolicy(time.Millisecond * 200)
testEnforceSync(t, e, "alice", "data1", "read", true)
testEnforceSync(t, e, "alice", "data1", "write", false)
testEnforceSync(t, e, "alice", "data2", "read", false)
testEnforceSync(t, e, "alice", "data2", "write", false)
testEnforceSync(t, e, "bob", "data1", "read", false)
testEnforceSync(t, e, "bob", "data1", "write", false)
testEnforceSync(t, e, "bob", "data2", "read", false)
testEnforceSync(t, e, "bob", "data2", "write", true)
// Simulate a policy change
e.ClearPolicy()
testEnforceSync(t, e, "bob", "data2", "write", false)
// Wait for at least one sync
time.Sleep(time.Millisecond * 300)
testEnforceSync(t, e, "bob", "data2", "write", true)
// Stop the reloading policy periodically.
e.StopAutoLoadPolicy()
}
func TestStopAutoLoadPolicy(t *testing.T) {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e.StartAutoLoadPolicy(5 * time.Millisecond)
if !e.IsAutoLoadingRunning() {
t.Error("auto load is not running")
}
e.StopAutoLoadPolicy()
// Need a moment, to exit goroutine
time.Sleep(10 * time.Millisecond)
if e.IsAutoLoadingRunning() {
t.Error("auto load is still running")
}
}
func testSyncedEnforcerGetPolicy(t *testing.T, e *SyncedEnforcer, res [][]string) {
t.Helper()
myRes, err := e.GetPolicy()
if err != nil {
t.Error(err)
}
if !util.SortedArray2DEquals(res, myRes) {
t.Error("Policy: ", myRes, ", supposed to be ", res)
} else {
t.Log("Policy: ", myRes)
}
}
func TestSyncedEnforcerSelfAddPolicy(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
}
}
func TestSyncedEnforcerSelfAddPolicies(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() {
_, _ = e.SelfAddPolicies("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
}()
go func() {
_, _ = e.SelfAddPolicies("p", "p", [][]string{{"user3", "data3", "read"}, {"user4", "data4", "read"}})
}()
go func() {
_, _ = e.SelfAddPolicies("p", "p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
}
}
func TestSyncedEnforcerSelfAddPoliciesEx(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
}()
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
}()
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user3", "data3", "read"}, {"user4", "data4", "read"}})
}()
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
}()
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
}()
go func() {
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user6", "data6", "read"}, {"user1", "data1", "read"}})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
}
}
func TestSyncedEnforcerSelfRemovePolicy(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfRemovePolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
})
}
}
func TestSyncedEnforcerSelfRemovePolicies(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
go func() {
_, _ = e.SelfRemovePolicies("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
}()
go func() {
_, _ = e.SelfRemovePolicies("p", "p", [][]string{{"user3", "data3", "read"}, {"user4", "data4", "read"}})
}()
go func() {
_, _ = e.SelfRemovePolicies("p", "p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
})
}
}
func TestSyncedEnforcerSelfRemoveFilteredPolicy(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user7", "data7", "write"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user8", "data8", "write"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
{"user7", "data7", "write"},
{"user8", "data8", "write"},
})
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 0, "user1") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 0, "user2") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 1, "data3") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 1, "data4") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 0, "user5") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 0, "user6") }()
go func() { _, _ = e.SelfRemoveFilteredPolicy("p", "p", 2, "write") }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
})
}
}
func TestSyncedEnforcerSelfUpdatePolicy(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user1", "data1", "read"}, []string{"user1", "data1", "write"})
}()
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user2", "data2", "read"}, []string{"user2", "data2", "write"})
}()
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user3", "data3", "read"}, []string{"user3", "data3", "write"})
}()
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user4", "data4", "read"}, []string{"user4", "data4", "write"})
}()
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user5", "data5", "read"}, []string{"user5", "data5", "write"})
}()
go func() {
_, _ = e.SelfUpdatePolicy("p", "p", []string{"user6", "data6", "read"}, []string{"user6", "data6", "write"})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "write"},
{"user2", "data2", "write"},
{"user3", "data3", "write"},
{"user4", "data4", "write"},
{"user5", "data5", "write"},
{"user6", "data6", "write"},
})
}
}
func TestSyncedEnforcerSelfUpdatePolicies(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user1", "data1", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user2", "data2", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user3", "data3", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user4", "data4", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user5", "data5", "read"}) }()
go func() { _, _ = e.SelfAddPolicy("p", "p", []string{"user6", "data6", "read"}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
go func() {
_, _ = e.SelfUpdatePolicies("p", "p",
[][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}},
[][]string{{"user1", "data1", "write"}, {"user2", "data2", "write"}})
}()
go func() {
_, _ = e.SelfUpdatePolicies("p", "p",
[][]string{{"user3", "data3", "read"}, {"user4", "data4", "read"}},
[][]string{{"user3", "data3", "write"}, {"user4", "data4", "write"}})
}()
go func() {
_, _ = e.SelfUpdatePolicies("p", "p",
[][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}},
[][]string{{"user5", "data5", "write"}, {"user6", "data6", "write"}})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "write"},
{"user2", "data2", "write"},
{"user3", "data3", "write"},
{"user4", "data4", "write"},
{"user5", "data5", "write"},
{"user6", "data6", "write"},
})
}
}
func TestSyncedEnforcerAddPoliciesEx(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() { _, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}}) }()
go func() { _, _ = e.AddPoliciesEx([][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
}
}
func TestSyncedEnforcerAddNamedPoliciesEx(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
}()
go func() {
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
}()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"user1", "data1", "read"},
{"user2", "data2", "read"},
{"user3", "data3", "read"},
{"user4", "data4", "read"},
{"user5", "data5", "read"},
{"user6", "data6", "read"},
})
}
}
func testSyncedEnforcerGetUsers(t *testing.T, e *SyncedEnforcer, res []string, name string, domain ...string) {
t.Helper()
myRes, err := e.GetUsersForRole(name, domain...)
myResCopy := make([]string, len(myRes))
copy(myResCopy, myRes)
sort.Strings(myRes)
sort.Strings(res)
switch err {
case nil:
break
case errors.ErrNameNotFound:
t.Log("No name found")
default:
t.Error("Users for ", name, " could not be fetched: ", err.Error())
}
t.Log("Users for ", name, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Users for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func TestSyncedEnforcerAddGroupingPoliciesEx(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
e.ClearPolicy()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user2", "member"}, {"user3", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user4", "member"}, {"user5", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user5", "member"}, {"user6", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user2", "member"}, {"user3", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user4", "member"}, {"user5", "member"}}) }()
go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user5", "member"}, {"user6", "member"}}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetUsers(t, e, []string{"user1", "user2", "user3", "user4", "user5", "user6"}, "member")
}
}
func TestSyncedEnforcerAddNamedGroupingPoliciesEx(t *testing.T) {
for i := 0; i < 10; i++ {
e, _ := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
e.ClearPolicy()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user2", "member"}, {"user3", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user4", "member"}, {"user5", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user5", "member"}, {"user6", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user2", "member"}, {"user3", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user4", "member"}, {"user5", "member"}}) }()
go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user5", "member"}, {"user6", "member"}}) }()
time.Sleep(100 * time.Millisecond)
testSyncedEnforcerGetUsers(t, e, []string{"user1", "user2", "user3", "user4", "user5", "user6"}, "member")
}
}
================================================
FILE: enforcer_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"strings"
"sync"
"testing"
"github.com/casbin/casbin/v3/detector"
"github.com/casbin/casbin/v3/model"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
"github.com/casbin/casbin/v3/util"
)
func TestKeyMatchModelInMemory(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")
a := fileadapter.NewAdapter("examples/keymatch_policy.csv")
e, _ := NewEnforcer(m, a)
testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource1", "POST", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "bob", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "POST", true)
testEnforce(t, e, "bob", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource2", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "GET", true)
testEnforce(t, e, "cathy", "/cathy_data", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "DELETE", false)
e, _ = NewEnforcer(m)
_ = a.LoadPolicy(e.GetModel())
testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource1", "POST", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "bob", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "POST", true)
testEnforce(t, e, "bob", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource2", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "GET", true)
testEnforce(t, e, "cathy", "/cathy_data", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "DELETE", false)
}
func TestKeyMatchWithRBACInDomain(t *testing.T) {
e, _ := NewEnforcer("examples/keymatch_with_rbac_in_domain.conf", "examples/keymatch_with_rbac_in_domain.csv")
testDomainEnforce(t, e, "Username==test2", "engines/engine1", "*", "attach", true)
}
func TestKeyMatchModelInMemoryDeny(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "!some(where (p.eft == deny))")
m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")
a := fileadapter.NewAdapter("examples/keymatch_policy.csv")
e, _ := NewEnforcer(m, a)
testEnforce(t, e, "alice", "/alice_data/resource2", "POST", true)
}
func TestRBACModelInMemoryIndeterminate(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")
e, _ := NewEnforcer(m)
_, _ = e.AddPermissionForUser("alice", "data1", "invalid")
testEnforce(t, e, "alice", "data1", "read", false)
}
func TestRBACModelInMemory(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")
e, _ := NewEnforcer(m)
_, _ = e.AddPermissionForUser("alice", "data1", "read")
_, _ = e.AddPermissionForUser("bob", "data2", "write")
_, _ = e.AddPermissionForUser("data2_admin", "data2", "read")
_, _ = e.AddPermissionForUser("data2_admin", "data2", "write")
_, _ = e.AddRoleForUser("alice", "data2_admin")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRBACModelInMemory2(t *testing.T) {
text :=
`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, _ := model.NewModelFromString(text)
// The above is the same as:
// m := NewModel()
// m.LoadModelFromText(text)
e, _ := NewEnforcer(m)
_, _ = e.AddPermissionForUser("alice", "data1", "read")
_, _ = e.AddPermissionForUser("bob", "data2", "write")
_, _ = e.AddPermissionForUser("data2_admin", "data2", "read")
_, _ = e.AddPermissionForUser("data2_admin", "data2", "write")
_, _ = e.AddRoleForUser("alice", "data2_admin")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestNotUsedRBACModelInMemory(t *testing.T) {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")
e, _ := NewEnforcer(m)
_, _ = e.AddPermissionForUser("alice", "data1", "read")
_, _ = e.AddPermissionForUser("bob", "data2", "write")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestMatcherUsingInOperator(t *testing.T) {
// From file config
e, _ := NewEnforcer("examples/rbac_model_matcher_using_in_op.conf")
_, _ = e.AddPermissionForUser("alice", "data1", "read")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data3", "read", true)
testEnforce(t, e, "anyone", "data1", "read", false)
testEnforce(t, e, "anyone", "data2", "read", true)
testEnforce(t, e, "anyone", "data3", "read", true)
}
func TestMatcherUsingInOperatorBracket(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model_matcher_using_in_op_bracket.conf")
_, _ = e.AddPermissionForUser("alice", "data1", "read")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data3", "read", true)
testEnforce(t, e, "anyone", "data1", "read", false)
testEnforce(t, e, "anyone", "data2", "read", true)
testEnforce(t, e, "anyone", "data3", "read", true)
}
func TestReloadPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
_ = e.LoadPolicy()
testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
}
func TestSavePolicy(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
_ = e.SavePolicy()
}
func TestClearPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
e.ClearPolicy()
}
func TestEnableEnforce(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e.EnableEnforce(false)
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", true)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", true)
testEnforce(t, e, "bob", "data1", "write", true)
testEnforce(t, e, "bob", "data2", "read", true)
testEnforce(t, e, "bob", "data2", "write", true)
e.EnableEnforce(true)
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestEnableLog(t *testing.T) {
// This test is now a no-op since the logger has been removed
// Keeping it for backward compatibility, but it just tests enforcement
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestEnableAutoSave(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e.EnableAutoSave(false)
// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
// it doesn't affect the policy in the storage.
_, _ = e.RemovePolicy("alice", "data1", "read")
// Reload the policy from the storage to see the effect.
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
e.EnableAutoSave(true)
// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
// but also affects the policy in the storage.
_, _ = e.RemovePolicy("alice", "data1", "read")
// However, the file adapter doesn't implement the AutoSave feature, so enabling it has no effect at all here.
// Reload the policy from the storage to see the effect.
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "data1", "read", true) // Will not be false here.
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestInitWithAdapter(t *testing.T) {
adapter := fileadapter.NewAdapter("examples/basic_policy.csv")
e, _ := NewEnforcer("examples/basic_model.conf", adapter)
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRoleLinks(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf")
e.EnableAutoBuildRoleLinks(false)
_ = e.BuildRoleLinks()
_, _ = e.Enforce("user501", "data9", "read")
}
func TestEnforceConcurrency(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("Enforce is not concurrent")
}
}()
e, _ := NewEnforcer("examples/rbac_model.conf")
_ = e.LoadModel()
var wg sync.WaitGroup
// Simulate concurrency (maybe use a timer?)
for i := 1; i <= 10000; i++ {
wg.Add(1)
go func() {
_, _ = e.Enforce("user501", "data9", "read")
wg.Done()
}()
}
wg.Wait()
}
func TestGetAndSetModel(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e2, _ := NewEnforcer("examples/basic_with_root_model.conf", "examples/basic_policy.csv")
testEnforce(t, e, "root", "data1", "read", false)
e.SetModel(e2.GetModel())
testEnforce(t, e, "root", "data1", "read", true)
}
func TestGetAndSetAdapterInMem(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e2, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_inverse_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
a2 := e2.GetAdapter()
e.SetAdapter(a2)
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "data1", "read", false)
testEnforce(t, e, "alice", "data1", "write", true)
}
func TestSetAdapterFromFile(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf")
testEnforce(t, e, "alice", "data1", "read", false)
a := fileadapter.NewAdapter("examples/basic_policy.csv")
e.SetAdapter(a)
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "data1", "read", true)
}
func TestInitEmpty(t *testing.T) {
e, _ := NewEnforcer()
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")
a := fileadapter.NewAdapter("examples/keymatch_policy.csv")
e.SetModel(m)
e.SetAdapter(a)
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
}
func testEnforceEx(t *testing.T, e *Enforcer, sub, obj, act interface{}, res []string) {
t.Helper()
_, myRes, _ := e.EnforceEx(sub, obj, act)
if ok := util.ArrayEquals(res, myRes); !ok {
t.Error("Key: ", myRes, ", supposed to be ", res)
}
}
func TestEnforceEx(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
testEnforceEx(t, e, "alice", "data1", "write", []string{})
testEnforceEx(t, e, "alice", "data2", "read", []string{})
testEnforceEx(t, e, "alice", "data2", "write", []string{})
testEnforceEx(t, e, "bob", "data1", "read", []string{})
testEnforceEx(t, e, "bob", "data1", "write", []string{})
testEnforceEx(t, e, "bob", "data2", "read", []string{})
testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})
e, _ = NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
testEnforceEx(t, e, "alice", "data1", "write", []string{})
testEnforceEx(t, e, "alice", "data2", "read", []string{"data2_admin", "data2", "read"})
testEnforceEx(t, e, "alice", "data2", "write", []string{"data2_admin", "data2", "write"})
testEnforceEx(t, e, "bob", "data1", "read", []string{})
testEnforceEx(t, e, "bob", "data1", "write", []string{})
testEnforceEx(t, e, "bob", "data2", "read", []string{})
testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})
e, _ = NewEnforcer("examples/priority_model.conf", "examples/priority_policy.csv")
testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read", "allow"})
testEnforceEx(t, e, "alice", "data1", "write", []string{"data1_deny_group", "data1", "write", "deny"})
testEnforceEx(t, e, "alice", "data2", "read", []string{})
testEnforceEx(t, e, "alice", "data2", "write", []string{})
testEnforceEx(t, e, "bob", "data1", "write", []string{})
testEnforceEx(t, e, "bob", "data2", "read", []string{"data2_allow_group", "data2", "read", "allow"})
testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write", "deny"})
e, _ = NewEnforcer("examples/abac_model.conf")
obj := struct{ Owner string }{Owner: "alice"}
testEnforceEx(t, e, "alice", obj, "write", []string{})
}
func TestEnforceExLog(t *testing.T) {
// This test was previously named for logging, but actually tests EnforceEx explain functionality
// Logger parameter has been removed, but the test still validates explain behavior
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
testEnforceEx(t, e, "alice", "data1", "write", []string{})
testEnforceEx(t, e, "alice", "data2", "read", []string{})
testEnforceEx(t, e, "alice", "data2", "write", []string{})
testEnforceEx(t, e, "bob", "data1", "read", []string{})
testEnforceEx(t, e, "bob", "data1", "write", []string{})
testEnforceEx(t, e, "bob", "data2", "read", []string{})
testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})
}
func testBatchEnforce(t *testing.T, e *Enforcer, requests [][]interface{}, results []bool) {
t.Helper()
myRes, _ := e.BatchEnforce(requests)
if len(myRes) != len(results) {
t.Errorf("%v supposed to be %v", myRes, results)
}
for i, v := range myRes {
if v != results[i] {
t.Errorf("%v supposed to be %v", myRes, results)
}
}
}
func TestBatchEnforce(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
results := []bool{true, true, false}
testBatchEnforce(t, e, [][]interface{}{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"jack", "data3", "read"}}, results)
}
func TestSubjectPriority(t *testing.T) {
e, _ := NewEnforcer("examples/subject_priority_model.conf", "examples/subject_priority_policy.csv")
testBatchEnforce(t, e, [][]interface{}{
{"jane", "data1", "read"},
{"alice", "data1", "read"},
}, []bool{
true, true,
})
}
func TestSubjectPriorityWithDomain(t *testing.T) {
e, _ := NewEnforcer("examples/subject_priority_model_with_domain.conf", "examples/subject_priority_policy_with_domain.csv")
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data1", "domain1", "write"},
{"bob", "data2", "domain2", "write"},
}, []bool{
true, true,
})
}
func TestSubjectPriorityInFilter(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/subject_priority_policy_with_domain.csv")
_ = e.InitWithAdapter("examples/subject_priority_model_with_domain.conf", adapter)
if err := e.loadFilteredPolicy(&fileadapter.Filter{
P: []string{"", "", "domain1"},
}); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data1", "domain1", "write"},
{"admin", "data1", "domain1", "write"},
}, []bool{
true, false,
})
}
func TestMultiplePolicyDefinitions(t *testing.T) {
e, _ := NewEnforcer("examples/multiple_policy_definitions_model.conf", "examples/multiple_policy_definitions_policy.csv")
enforceContext := NewEnforceContext("2")
enforceContext.EType = "e"
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data2", "read"},
{enforceContext, struct{ Age int }{Age: 70}, "/data1", "read"},
{enforceContext, struct{ Age int }{Age: 30}, "/data1", "read"},
}, []bool{
true, false, true,
})
}
func TestPriorityExplicit(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data1", "write"},
{"alice", "data1", "read"},
{"bob", "data2", "read"},
{"bob", "data2", "write"},
{"data1_deny_group", "data1", "read"},
{"data1_deny_group", "data1", "write"},
{"data2_allow_group", "data2", "read"},
{"data2_allow_group", "data2", "write"},
}, []bool{
true, true, false, true, false, false, true, true,
})
_, err := e.AddPolicy("1", "bob", "data2", "write", "deny")
if err != nil {
t.Fatalf("Add Policy: %v", err)
}
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data1", "write"},
{"alice", "data1", "read"},
{"bob", "data2", "read"},
{"bob", "data2", "write"},
{"data1_deny_group", "data1", "read"},
{"data1_deny_group", "data1", "write"},
{"data2_allow_group", "data2", "read"},
{"data2_allow_group", "data2", "write"},
}, []bool{
true, true, false, false, false, false, true, true,
})
}
func TestFailedToLoadPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
e.AddNamedMatchingFunc("g2", "matchingFunc", util.KeyMatch2)
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "bob", "/pen/3", "GET", true)
e.SetAdapter(fileadapter.NewAdapter("not found"))
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "bob", "/pen/3", "GET", true)
}
func TestReloadPolicyWithFunc(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
e.AddNamedMatchingFunc("g2", "matchingFunc", util.KeyMatch2)
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "bob", "/pen/3", "GET", true)
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "bob", "/pen/3", "GET", true)
}
func TestEvalPriority(t *testing.T) {
e, _ := NewEnforcer("examples/eval_operator_model.conf", "examples/eval_operator_policy.csv")
testEnforce(t, e, "admin", "users", "write", true)
testEnforce(t, e, "admin", "none", "write", false)
testEnforce(t, e, "user", "users", "write", false)
}
func TestLinkConditionFunc(t *testing.T) {
TrueFunc := func(args ...string) (bool, error) {
if len(args) != 0 {
return args[0] == "_" || args[0] == "true", nil
}
return false, nil
}
FalseFunc := func(args ...string) (bool, error) {
if len(args) != 0 {
return args[0] == "_" || args[0] == "false", nil
}
return false, nil
}
m, _ := model.NewModelFromFile("examples/rbac_with_temporal_roles_model.conf")
e, _ := NewEnforcer(m)
_, _ = e.AddPolicies([][]string{
{"alice", "data1", "read"},
{"alice", "data1", "write"},
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"},
{"data3_admin", "data3", "read"},
{"data3_admin", "data3", "write"},
{"data4_admin", "data4", "read"},
{"data4_admin", "data4", "write"},
{"data5_admin", "data5", "read"},
{"data5_admin", "data5", "write"},
})
_, _ = e.AddGroupingPolicies([][]string{
{"alice", "data2_admin", "_", "_"},
{"alice", "data3_admin", "_", "_"},
{"alice", "data4_admin", "_", "_"},
{"alice", "data5_admin", "_", "_"},
})
e.AddNamedLinkConditionFunc("g", "alice", "data2_admin", TrueFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data3_admin", TrueFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data4_admin", FalseFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data5_admin", FalseFunc)
e.SetNamedLinkConditionFuncParams("g", "alice", "data2_admin", "true")
e.SetNamedLinkConditionFuncParams("g", "alice", "data3_admin", "not true")
e.SetNamedLinkConditionFuncParams("g", "alice", "data4_admin", "false")
e.SetNamedLinkConditionFuncParams("g", "alice", "data5_admin", "not false")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", true)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "alice", "data3", "read", false)
testEnforce(t, e, "alice", "data3", "write", false)
testEnforce(t, e, "alice", "data4", "read", true)
testEnforce(t, e, "alice", "data4", "write", true)
testEnforce(t, e, "alice", "data5", "read", false)
testEnforce(t, e, "alice", "data5", "write", false)
m, _ = model.NewModelFromFile("examples/rbac_with_domain_temporal_roles_model.conf")
e, _ = NewEnforcer(m)
_, _ = e.AddPolicies([][]string{
{"alice", "domain1", "data1", "read"},
{"alice", "domain1", "data1", "write"},
{"data2_admin", "domain2", "data2", "read"},
{"data2_admin", "domain2", "data2", "write"},
{"data3_admin", "domain3", "data3", "read"},
{"data3_admin", "domain3", "data3", "write"},
{"data4_admin", "domain4", "data4", "read"},
{"data4_admin", "domain4", "data4", "write"},
{"data5_admin", "domain5", "data5", "read"},
{"data5_admin", "domain5", "data5", "write"},
})
_, _ = e.AddGroupingPolicies([][]string{
{"alice", "data2_admin", "domain2", "_", "_"},
{"alice", "data3_admin", "domain3", "_", "_"},
{"alice", "data4_admin", "domain4", "_", "_"},
{"alice", "data5_admin", "domain5", "_", "_"},
})
e.AddNamedDomainLinkConditionFunc("g", "alice", "data2_admin", "domain2", TrueFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data3_admin", "domain3", TrueFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data4_admin", "domain4", FalseFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data5_admin", "domain5", FalseFunc)
e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data2_admin", "domain2", "true")
e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data3_admin", "domain3", "not true")
e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data4_admin", "domain4", "false")
e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data5_admin", "domain5", "not false")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
testDomainEnforce(t, e, "alice", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "alice", "domain2", "data2", "write", true)
testDomainEnforce(t, e, "alice", "domain3", "data3", "read", false)
testDomainEnforce(t, e, "alice", "domain3", "data3", "write", false)
testDomainEnforce(t, e, "alice", "domain4", "data4", "read", true)
testDomainEnforce(t, e, "alice", "domain4", "data4", "write", true)
testDomainEnforce(t, e, "alice", "domain5", "data5", "read", false)
testDomainEnforce(t, e, "alice", "domain5", "data5", "write", false)
}
func TestEnforcerWithDefaultDetector(t *testing.T) {
// Test that default detector is enabled and detects cycles
_, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_cycle_policy.csv")
// Expect an error because the policy contains a cycle
if err == nil {
t.Error("Expected cycle detection error when loading policy with cycle, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
}
}
}
func TestEnforcerRunDetections(t *testing.T) {
// Test explicit RunDetections() call
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
// Should not error on valid policy
err := e.RunDetections()
if err != nil {
t.Errorf("Expected no error when running detections on valid policy, but got: %v", err)
}
// Now add a cycle manually
_, _ = e.AddGroupingPolicy("alice", "data2_admin")
_, _ = e.AddGroupingPolicy("data2_admin", "super_admin")
_, _ = e.AddGroupingPolicy("super_admin", "alice")
// Should detect the cycle
err = e.RunDetections()
if err == nil {
t.Error("Expected cycle detection error, but got nil")
} else {
errMsg := err.Error()
if !strings.Contains(errMsg, "cycle detected") {
t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
}
}
}
func TestEnforcerSetDetector(t *testing.T) {
// Test SetDetector() method
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
// Create a custom detector
customDetector := detector.NewDefaultDetector()
e.SetDetector(customDetector)
// Should still work with custom detector
err := e.RunDetections()
if err != nil {
t.Errorf("Expected no error with custom detector, but got: %v", err)
}
}
func TestEnforcerSetDetectors(t *testing.T) {
// Test SetDetectors() method
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
// Create multiple detectors
detectors := []detector.Detector{
detector.NewDefaultDetector(),
detector.NewDefaultDetector(),
}
e.SetDetectors(detectors)
// Should work with multiple detectors
err := e.RunDetections()
if err != nil {
t.Errorf("Expected no error with multiple detectors, but got: %v", err)
}
}
================================================
FILE: enforcer_transactional.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"errors"
"sync"
"sync/atomic"
"time"
"github.com/casbin/casbin/v3/persist"
"github.com/google/uuid"
)
// TransactionalEnforcer extends Enforcer with transaction support.
// It provides atomic policy operations through transactions.
type TransactionalEnforcer struct {
*Enforcer // Embedded enforcer for all standard functionality
activeTransactions sync.Map // Stores active transactions.
modelVersion int64 // Model version number for optimistic locking.
commitLock sync.Mutex // Protects commit and rollback operations.
}
// NewTransactionalEnforcer creates a new TransactionalEnforcer.
// It accepts the same parameters as NewEnforcer.
func NewTransactionalEnforcer(params ...interface{}) (*TransactionalEnforcer, error) {
enforcer, err := NewEnforcer(params...)
if err != nil {
return nil, err
}
return &TransactionalEnforcer{
Enforcer: enforcer,
}, nil
}
// BeginTransaction starts a new transaction.
// Returns an error if a transaction is already in progress or if the adapter doesn't support transactions.
func (te *TransactionalEnforcer) BeginTransaction(ctx context.Context) (*Transaction, error) {
// Check if adapter supports transactions.
txAdapter, ok := te.adapter.(persist.TransactionalAdapter)
if !ok {
return nil, errors.New("adapter does not support transactions")
}
// Start database transaction.
txContext, err := txAdapter.BeginTransaction(ctx)
if err != nil {
return nil, err
}
// Create transaction buffer with current model snapshot.
buffer := NewTransactionBuffer(te.model)
tx := &Transaction{
id: uuid.New().String(),
enforcer: te,
buffer: buffer,
txContext: txContext,
ctx: ctx,
baseVersion: atomic.LoadInt64(&te.modelVersion),
startTime: time.Now(),
}
te.activeTransactions.Store(tx.id, tx)
return tx, nil
}
// GetTransaction returns a transaction by its ID, or nil if not found.
func (te *TransactionalEnforcer) GetTransaction(id string) *Transaction {
if tx, ok := te.activeTransactions.Load(id); ok {
return tx.(*Transaction)
}
return nil
}
// IsTransactionActive returns true if the transaction with the given ID is active.
func (te *TransactionalEnforcer) IsTransactionActive(id string) bool {
if tx := te.GetTransaction(id); tx != nil {
return tx.IsActive()
}
return false
}
// WithTransaction executes a function within a transaction.
// If the function returns an error, the transaction is rolled back.
// Otherwise, it's committed automatically.
func (te *TransactionalEnforcer) WithTransaction(ctx context.Context, fn func(*Transaction) error) error {
tx, err := te.BeginTransaction(ctx)
if err != nil {
return err
}
defer func() {
if r := recover(); r != nil {
_ = tx.Rollback()
panic(r)
}
}()
err = fn(tx)
if err != nil {
_ = tx.Rollback()
return err
}
return tx.Commit()
}
================================================
FILE: error_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
)
func TestPathError(t *testing.T) {
_, err := NewEnforcer("hope_this_path_wont_exist", "")
if err == nil {
t.Errorf("Should be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
}
func TestEnforcerParamError(t *testing.T) {
_, err := NewEnforcer(1, 2, 3)
if err == nil {
t.Errorf("Should not be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
_, err2 := NewEnforcer(1, "2")
if err2 == nil {
t.Errorf("Should not be error here.")
} else {
t.Log("Test on error: ")
t.Log(err2.Error())
}
}
func TestModelError(t *testing.T) {
_, err := NewEnforcer("examples/error/error_model.conf", "examples/error/error_policy.csv")
if err == nil {
t.Errorf("Should be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
}
// func TestPolicyError(t *testing.T) {
// _, err := NewEnforcer("examples/basic_model.conf", "examples/error/error_policy.csv")
// if err == nil {
// t.Errorf("Should be error here.")
// } else {
// t.Log("Test on error: ")
// t.Log(err.Error())
// }
//}
func TestEnforceError(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
_, err := e.Enforce("wrong", "wrong")
if err == nil {
t.Errorf("Should be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
e, _ = NewEnforcer("examples/abac_rule_model.conf")
_, err = e.Enforce("wang", "wang", "wang")
if err == nil {
t.Errorf("Should be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
}
func TestNoError(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
err := e.LoadModel()
if err != nil {
t.Errorf("Should be no error here.")
t.Log("Unexpected error: ")
t.Log(err.Error())
}
err = e.LoadPolicy()
if err != nil {
t.Errorf("Should be no error here.")
t.Log("Unexpected error: ")
t.Log(err.Error())
}
err = e.SavePolicy()
if err != nil {
t.Errorf("Should be no error here.")
t.Log("Unexpected error: ")
t.Log(err.Error())
}
}
func TestModelNoError(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
e.modelPath = "hope_this_path_wont_exist"
err := e.LoadModel()
if err == nil {
t.Errorf("Should be error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
}
func TestMockAdapterErrors(t *testing.T) {
adapter := fileadapter.NewAdapterMock("examples/rbac_with_domains_policy.csv")
adapter.SetMockErr("mock error")
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", adapter)
added, err := e.AddPolicy("admin", "domain3", "data1", "read")
if added {
t.Errorf("added should be false")
}
if err == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
rules := [][]string{
{"admin", "domain4", "data1", "read"},
}
added, err = e.AddPolicies(rules)
if added {
t.Errorf("added should be false")
}
if err == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
removed, err2 := e.RemoveFilteredPolicy(1, "domain1", "data1")
if removed {
t.Errorf("removed should be false")
}
if err2 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err2.Error())
}
removed, err3 := e.RemovePolicy("admin", "domain2", "data2", "read")
if removed {
t.Errorf("removed should be false")
}
if err3 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err3.Error())
}
rules = [][]string{
{"admin", "domain1", "data1", "read"},
}
removed, err = e.RemovePolicies(rules)
if removed {
t.Errorf("removed should be false")
}
if err == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err.Error())
}
added, err4 := e.AddGroupingPolicy("bob", "admin2")
if added {
t.Errorf("added should be false")
}
if err4 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err4.Error())
}
added, err5 := e.AddNamedGroupingPolicy("g", []string{"eve", "admin2", "domain1"})
if added {
t.Errorf("added should be false")
}
if err5 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err5.Error())
}
added, err6 := e.AddNamedPolicy("p", []string{"admin2", "domain2", "data2", "write"})
if added {
t.Errorf("added should be false")
}
if err6 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err6.Error())
}
removed, err7 := e.RemoveGroupingPolicy("bob", "admin2")
if removed {
t.Errorf("removed should be false")
}
if err7 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err7.Error())
}
removed, err8 := e.RemoveFilteredGroupingPolicy(0, "bob")
if removed {
t.Errorf("removed should be false")
}
if err8 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err8.Error())
}
removed, err9 := e.RemoveNamedGroupingPolicy("g", []string{"alice", "admin", "domain1"})
if removed {
t.Errorf("removed should be false")
}
if err9 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err9.Error())
}
removed, err10 := e.RemoveFilteredNamedGroupingPolicy("g", 0, "eve")
if removed {
t.Errorf("removed should be false")
}
if err10 == nil {
t.Errorf("Should be an error here.")
} else {
t.Log("Test on error: ")
t.Log(err10.Error())
}
}
================================================
FILE: errors/constraint_errors.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import (
"errors"
"fmt"
)
// Global errors for constraints defined here.
var (
ErrConstraintViolation = errors.New("constraint violation")
ErrConstraintParsingError = errors.New("constraint parsing error")
ErrConstraintRequiresRBAC = errors.New("constraints require RBAC to be enabled (role_definition section must exist)")
ErrInvalidConstraintDefinition = errors.New("invalid constraint definition")
)
// ConstraintViolationError represents a specific constraint violation.
type ConstraintViolationError struct {
ConstraintName string
Message string
}
func (e *ConstraintViolationError) Error() string {
return fmt.Sprintf("constraint violation [%s]: %s", e.ConstraintName, e.Message)
}
// NewConstraintViolationError creates a new constraint violation error.
func NewConstraintViolationError(constraintName, message string) error {
return &ConstraintViolationError{
ConstraintName: constraintName,
Message: message,
}
}
================================================
FILE: errors/rbac_errors.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package errors
import "errors"
// Global errors for rbac defined here.
var (
ErrNameNotFound = errors.New("error: name does not exist")
ErrDomainParameter = errors.New("error: domain should be 1 parameter")
ErrLinkNotFound = errors.New("error: link between name1 and name2 does not exist")
ErrUseDomainParameter = errors.New("error: useDomain should be 1 parameter")
ErrInvalidFieldValuesParameter = errors.New("fieldValues requires at least one parameter")
// GetAllowedObjectConditions errors.
ErrObjCondition = errors.New("need to meet the prefix required by the object condition")
ErrEmptyCondition = errors.New("GetAllowedObjectConditions have an empty condition")
)
================================================
FILE: examples/abac_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == r.obj.Owner
================================================
FILE: examples/abac_not_using_policy_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = r.sub == r.obj.Owner
================================================
FILE: examples/abac_rule_effect_policy.csv
================================================
p, alice, /data1, read, deny
p, alice, /data1, write, allow
p, bob, /data2, write, deny
p, bob, /data2, read, allow
================================================
FILE: examples/abac_rule_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/abac_rule_policy.csv
================================================
p, r.sub.Age > 18, /data1, read
p, r.sub.Age < 60, /data2, write
================================================
FILE: examples/basic_inverse_policy.csv
================================================
p, alice, data1, write
p, bob, data2, read
================================================
FILE: examples/basic_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/basic_model_without_spaces.conf
================================================
[request_definition]
r = sub,obj,act
[policy_definition]
p = sub,obj,act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/basic_policy.csv
================================================
p, alice, data1, read
p, bob, data2, write
================================================
FILE: examples/basic_with_root_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
================================================
FILE: examples/basic_without_resources_model.conf
================================================
[request_definition]
r = sub, act
[policy_definition]
p = sub, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.act == p.act
================================================
FILE: examples/basic_without_resources_policy.csv
================================================
p, alice, read
p, bob, write
================================================
FILE: examples/basic_without_users_model.conf
================================================
[request_definition]
r = obj, act
[policy_definition]
p = obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.obj == p.obj && r.act == p.act
================================================
FILE: examples/basic_without_users_policy.csv
================================================
p, data1, read
p, data2, write
================================================
FILE: examples/biba_model.conf
================================================
[request_definition]
r = sub, sub_level, obj, obj_level, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m =(r.act == "read" && r.sub_level <= r.obj_level) || (r.act == "write" && r.sub_level >= r.obj_level)
================================================
FILE: examples/blp_model.conf
================================================
[request_definition]
r = sub, sub_level, obj, obj_level, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (r.act == "read" && r.sub_level >= r.obj_level) || (r.act == "write" && r.sub_level <= r.obj_level)
================================================
FILE: examples/comment_model.conf
================================================
[request_definition]
r = sub, obj, act ; Request definition
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow)) # This is policy effect.
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/error/error_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/error/error_policy.csv
================================================
p, alice, data1, read
bob, data2, write
================================================
FILE: examples/eval_operator_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj_rule, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && eval(p.obj_rule) && (p.act == '*' || r.act == p.act)
================================================
FILE: examples/eval_operator_policy.csv
================================================
p, r.sub == 'admin' || false, r.obj == 'users', write
================================================
FILE: examples/glob_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && globMatch(r.obj, p.obj) && r.act == p.act
================================================
FILE: examples/glob_policy.csv
================================================
p, u1, /foo/*, read
p, u2, /foo*, read
p, u3, /*/foo/*, read
p, u4, *, read
================================================
FILE: examples/ipmatch_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/ipmatch_policy.csv
================================================
p, 192.168.2.0/24, data1, read
p, 10.0.0.0/16, data2, write
================================================
FILE: examples/keyget2_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyGet2(r.obj, p.obj, 'resource') in ('age', 'name') && regexMatch(r.act, p.act)
================================================
FILE: examples/keyget_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && (r.obj == p.obj || keyGet(r.obj, p.obj) in ('age','name')) && regexMatch(r.act, p.act)
================================================
FILE: examples/keymatch2_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/keymatch2_policy.csv
================================================
p, alice, /alice_data/:resource, GET
p, alice, /alice_data2/:id/using/:resId, GET
================================================
FILE: examples/keymatch_custom_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatchCustom(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/keymatch_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/keymatch_policy.csv
================================================
p, alice, /alice_data/*, GET
p, alice, /alice_data/resource1, POST
p, bob, /alice_data/resource2, GET
p, bob, /bob_data/*, POST
p, cathy, /cathy_data, (GET)|(POST)
================================================
FILE: examples/keymatch_with_rbac_in_domain.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/keymatch_with_rbac_in_domain.csv
================================================
g, can_manage, can_use, *
p, can_manage, engines/*, *, (pause)|(resume)
p, can_use, engines/*, *, (attach)|(detach)
g, Username==test2, can_manage, engines/engine1
================================================
FILE: examples/lbac_model.conf
================================================
[request_definition]
r = sub, subject_confidentiality, subject_integrity, obj, object_confidentiality, object_integrity, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (r.act == "read" && r.subject_confidentiality >= r.object_confidentiality && r.subject_integrity >= r.object_integrity) || (r.act == "write" && r.subject_confidentiality <= r.object_confidentiality && r.subject_integrity <= r.object_integrity)
================================================
FILE: examples/multiple_policy_definitions_model.conf
================================================
[request_definition]
r = sub, obj, act
r2 = sub, obj, act
[policy_definition]
p = sub, obj, act
p2= sub_rule, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
#RABC
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
#ABAC
m2 = eval(p2.sub_rule) && r2.obj == p2.obj && r2.act == p2.act
================================================
FILE: examples/multiple_policy_definitions_policy.csv
================================================
p, data2_admin, data2, read
p2, r2.sub.Age > 18 && r2.sub.Age < 60, /data1, read, allow
p2, r2.sub.Age > 60 && r2.sub.Age < 100, /data1, read, deny
g, alice, data2_admin
================================================
FILE: examples/object_conditions_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, sub_rule, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && eval(p.sub_rule) && r.act == p.act
================================================
FILE: examples/object_conditions_policy.csv
================================================
p, alice, r.obj.price < 25, read
p, admin, r.obj.category_id = 2, read
p, bob, r.obj.author = bob, write
g, alice, admin
================================================
FILE: examples/orbac_model.conf
================================================
[request_definition]
r = sub, org, obj, act
[policy_definition]
p = role, activity, view, org
[role_definition]
g = _, _, _
g2 = _, _, _
g3 = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.role, r.org) && g2(r.act, p.activity, r.org) && g3(r.obj, p.view, r.org) && r.org == p.org
================================================
FILE: examples/orbac_policy.csv
================================================
# Permission rules: role, activity, view, organization
p, manager, modify, document, org1
p, manager, consult, document, org1
p, employee, consult, document, org1
p, manager, modify, report, org2
p, manager, consult, report, org2
p, employee, consult, report, org2
# Empower: subject, role, organization
g, alice, manager, org1
g, bob, employee, org1
g, charlie, manager, org2
g, david, employee, org2
# Use: action, activity, organization
g2, write, modify, org1
g2, read, consult, org1
g2, write, modify, org2
g2, read, consult, org2
# Consider: object, view, organization
g3, data1, document, org1
g3, data2, document, org1
g3, report1, report, org2
g3, report2, report, org2
================================================
FILE: examples/pbac_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj_rule, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && eval(p.obj_rule) && r.act == p.act
================================================
FILE: examples/pbac_policy.csv
================================================
p, r.sub.Role == 'admin', r.obj.Type == 'doc', read
p, r.sub.Age >= 18, r.obj.Type == 'video', play
================================================
FILE: examples/performance/rbac_with_pattern_large_scale_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.obj) && keyMatch4(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/performance/rbac_with_pattern_large_scale_policy.csv
================================================
# 132 policies / 3000 grouping policies / 300 subjuects / 6 roles
# Policy - staff001
p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1006
p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1007
p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1008
p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1009
p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1010
p, staff001, /orgs/{orgID}/sites, App003.*.Action3001
p, staff001, /orgs/{orgID}/sites, App003.*.Action3002
p, staff001, /orgs/{orgID}/sites, App003.*.Action3003
p, staff001, /orgs/{orgID}/sites, App003.*.Action3004
p, staff001, /orgs/{orgID}/sites, App003.*.Action3005
p, staff001, /orgs/{orgID}, App004.*
p, staff001, /orgs, App005.*
# Policy - staff002
p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1006
p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1007
p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1008
p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1009
p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1010
p, staff002, /orgs/{orgID}/sites, App003.*.Action3001
p, staff002, /orgs/{orgID}/sites, App003.*.Action3002
p, staff002, /orgs/{orgID}/sites, App003.*.Action3003
p, staff002, /orgs/{orgID}/sites, App003.*.Action3004
p, staff002, /orgs/{orgID}/sites, App003.*.Action3005
p, staff002, /orgs/{orgID}, App004.*
p, staff002, /orgs, App005.*
# Policy - manager001
p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1006
p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1007
p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1008
p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1009
p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1010
p, manager001, /orgs/{orgID}/sites, App003.*.Action3001
p, manager001, /orgs/{orgID}/sites, App003.*.Action3002
p, manager001, /orgs/{orgID}/sites, App003.*.Action3003
p, manager001, /orgs/{orgID}/sites, App003.*.Action3004
p, manager001, /orgs/{orgID}/sites, App003.*.Action3005
p, manager001, /orgs/{orgID}, App004.*
p, manager001, /orgs, App005.*
# Policy - manager002
p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1006
p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1007
p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1008
p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1009
p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1010
p, manager002, /orgs/{orgID}/sites, App003.*.Action3001
p, manager002, /orgs/{orgID}/sites, App003.*.Action3002
p, manager002, /orgs/{orgID}/sites, App003.*.Action3003
p, manager002, /orgs/{orgID}/sites, App003.*.Action3004
p, manager002, /orgs/{orgID}/sites, App003.*.Action3005
p, manager002, /orgs/{orgID}, App004.*
p, manager002, /orgs, App005.*
# Policy - customer001
p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1006
p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1007
p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1008
p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1009
p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1010
p, customer001, /orgs/{orgID}/sites, App003.*.Action3001
p, customer001, /orgs/{orgID}/sites, App003.*.Action3002
p, customer001, /orgs/{orgID}/sites, App003.*.Action3003
p, customer001, /orgs/{orgID}/sites, App003.*.Action3004
p, customer001, /orgs/{orgID}/sites, App003.*.Action3005
p, customer001, /orgs/{orgID}, App004.*
p, customer001, /orgs, App005.*
# Policy - customer002
p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001
p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002
p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003
p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004
p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005
p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001
p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002
p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003
p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004
p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005
p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1006
p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1007
p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1008
p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1009
p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1010
p, customer002, /orgs/{orgID}/sites, App003.*.Action3001
p, customer002, /orgs/{orgID}/sites, App003.*.Action3002
p, customer002, /orgs/{orgID}/sites, App003.*.Action3003
p, customer002, /orgs/{orgID}/sites, App003.*.Action3004
p, customer002, /orgs/{orgID}/sites, App003.*.Action3005
p, customer002, /orgs/{orgID}, App004.*
p, customer002, /orgs, App005.*
# Group - staff001, / org1
g, staffUser1001, staff001, /orgs/1/sites/site001
g, staffUser1001, staff001, /orgs/1/sites/site002
g, staffUser1001, staff001, /orgs/1/sites/site003
g, staffUser1001, staff001, /orgs/1/sites/site004
g, staffUser1001, staff001, /orgs/1/sites/site005
g, staffUser1001, staff001, /orgs/1/sites/site001
g, staffUser1001, staff001, /orgs/1/sites/site002
g, staffUser1001, staff001, /orgs/1/sites/site003
g, staffUser1001, staff001, /orgs/1/sites/site004
g, staffUser1001, staff001, /orgs/1/sites/site005
g, staffUser1003, staff001, /orgs/1/sites/site001
g, staffUser1003, staff001, /orgs/1/sites/site002
g, staffUser1003, staff001, /orgs/1/sites/site003
g, staffUser1003, staff001, /orgs/1/sites/site004
g, staffUser1003, staff001, /orgs/1/sites/site005
g, staffUser1004, staff001, /orgs/1/sites/site001
g, staffUser1004, staff001, /orgs/1/sites/site002
g, staffUser1004, staff001, /orgs/1/sites/site003
g, staffUser1004, staff001, /orgs/1/sites/site004
g, staffUser1004, staff001, /orgs/1/sites/site005
g, staffUser1005, staff001, /orgs/1/sites/site001
g, staffUser1005, staff001, /orgs/1/sites/site002
g, staffUser1005, staff001, /orgs/1/sites/site003
g, staffUser1005, staff001, /orgs/1/sites/site004
g, staffUser1005, staff001, /orgs/1/sites/site005
g, staffUser1006, staff001, /orgs/1/sites/site001
g, staffUser1006, staff001, /orgs/1/sites/site002
g, staffUser1006, staff001, /orgs/1/sites/site003
g, staffUser1006, staff001, /orgs/1/sites/site004
g, staffUser1006, staff001, /orgs/1/sites/site005
g, staffUser1007, staff001, /orgs/1/sites/site001
g, staffUser1007, staff001, /orgs/1/sites/site002
g, staffUser1007, staff001, /orgs/1/sites/site003
g, staffUser1007, staff001, /orgs/1/sites/site004
g, staffUser1007, staff001, /orgs/1/sites/site005
g, staffUser1008, staff001, /orgs/1/sites/site001
g, staffUser1008, staff001, /orgs/1/sites/site002
g, staffUser1008, staff001, /orgs/1/sites/site003
g, staffUser1008, staff001, /orgs/1/sites/site004
g, staffUser1008, staff001, /orgs/1/sites/site005
g, staffUser1009, staff001, /orgs/1/sites/site001
g, staffUser1009, staff001, /orgs/1/sites/site002
g, staffUser1009, staff001, /orgs/1/sites/site003
g, staffUser1009, staff001, /orgs/1/sites/site004
g, staffUser1009, staff001, /orgs/1/sites/site005
g, staffUser1010, staff001, /orgs/1/sites/site001
g, staffUser1010, staff001, /orgs/1/sites/site002
g, staffUser1010, staff001, /orgs/1/sites/site003
g, staffUser1010, staff001, /orgs/1/sites/site004
g, staffUser1010, staff001, /orgs/1/sites/site005
g, staffUser1011, staff001, /orgs/1/sites/site001
g, staffUser1011, staff001, /orgs/1/sites/site002
g, staffUser1011, staff001, /orgs/1/sites/site003
g, staffUser1011, staff001, /orgs/1/sites/site004
g, staffUser1011, staff001, /orgs/1/sites/site005
g, staffUser1012, staff001, /orgs/1/sites/site001
g, staffUser1012, staff001, /orgs/1/sites/site002
g, staffUser1012, staff001, /orgs/1/sites/site003
g, staffUser1012, staff001, /orgs/1/sites/site004
g, staffUser1012, staff001, /orgs/1/sites/site005
g, staffUser1013, staff001, /orgs/1/sites/site001
g, staffUser1013, staff001, /orgs/1/sites/site002
g, staffUser1013, staff001, /orgs/1/sites/site003
g, staffUser1013, staff001, /orgs/1/sites/site004
g, staffUser1013, staff001, /orgs/1/sites/site005
g, staffUser1014, staff001, /orgs/1/sites/site001
g, staffUser1014, staff001, /orgs/1/sites/site002
g, staffUser1014, staff001, /orgs/1/sites/site003
g, staffUser1014, staff001, /orgs/1/sites/site004
g, staffUser1014, staff001, /orgs/1/sites/site005
g, staffUser1015, staff001, /orgs/1/sites/site001
g, staffUser1015, staff001, /orgs/1/sites/site002
g, staffUser1015, staff001, /orgs/1/sites/site003
g, staffUser1015, staff001, /orgs/1/sites/site004
g, staffUser1015, staff001, /orgs/1/sites/site005
g, staffUser1016, staff001, /orgs/1/sites/site001
g, staffUser1016, staff001, /orgs/1/sites/site002
g, staffUser1016, staff001, /orgs/1/sites/site003
g, staffUser1016, staff001, /orgs/1/sites/site004
g, staffUser1016, staff001, /orgs/1/sites/site005
g, staffUser1017, staff001, /orgs/1/sites/site001
g, staffUser1017, staff001, /orgs/1/sites/site002
g, staffUser1017, staff001, /orgs/1/sites/site003
g, staffUser1017, staff001, /orgs/1/sites/site004
g, staffUser1017, staff001, /orgs/1/sites/site005
g, staffUser1018, staff001, /orgs/1/sites/site001
g, staffUser1018, staff001, /orgs/1/sites/site002
g, staffUser1018, staff001, /orgs/1/sites/site003
g, staffUser1018, staff001, /orgs/1/sites/site004
g, staffUser1018, staff001, /orgs/1/sites/site005
g, staffUser1019, staff001, /orgs/1/sites/site001
g, staffUser1019, staff001, /orgs/1/sites/site002
g, staffUser1019, staff001, /orgs/1/sites/site003
g, staffUser1019, staff001, /orgs/1/sites/site004
g, staffUser1019, staff001, /orgs/1/sites/site005
g, staffUser1020, staff001, /orgs/1/sites/site001
g, staffUser1020, staff001, /orgs/1/sites/site002
g, staffUser1020, staff001, /orgs/1/sites/site003
g, staffUser1020, staff001, /orgs/1/sites/site004
g, staffUser1020, staff001, /orgs/1/sites/site005
g, staffUser1021, staff001, /orgs/1/sites/site001
g, staffUser1021, staff001, /orgs/1/sites/site002
g, staffUser1021, staff001, /orgs/1/sites/site003
g, staffUser1021, staff001, /orgs/1/sites/site004
g, staffUser1021, staff001, /orgs/1/sites/site005
g, staffUser1022, staff001, /orgs/1/sites/site001
g, staffUser1022, staff001, /orgs/1/sites/site002
g, staffUser1022, staff001, /orgs/1/sites/site003
g, staffUser1022, staff001, /orgs/1/sites/site004
g, staffUser1022, staff001, /orgs/1/sites/site005
g, staffUser1023, staff001, /orgs/1/sites/site001
g, staffUser1023, staff001, /orgs/1/sites/site002
g, staffUser1023, staff001, /orgs/1/sites/site003
g, staffUser1023, staff001, /orgs/1/sites/site004
g, staffUser1023, staff001, /orgs/1/sites/site005
g, staffUser1024, staff001, /orgs/1/sites/site001
g, staffUser1024, staff001, /orgs/1/sites/site002
g, staffUser1024, staff001, /orgs/1/sites/site003
g, staffUser1024, staff001, /orgs/1/sites/site004
g, staffUser1024, staff001, /orgs/1/sites/site005
g, staffUser1025, staff001, /orgs/1/sites/site001
g, staffUser1025, staff001, /orgs/1/sites/site002
g, staffUser1025, staff001, /orgs/1/sites/site003
g, staffUser1025, staff001, /orgs/1/sites/site004
g, staffUser1025, staff001, /orgs/1/sites/site005
g, staffUser1026, staff001, /orgs/1/sites/site001
g, staffUser1026, staff001, /orgs/1/sites/site002
g, staffUser1026, staff001, /orgs/1/sites/site003
g, staffUser1026, staff001, /orgs/1/sites/site004
g, staffUser1026, staff001, /orgs/1/sites/site005
g, staffUser1027, staff001, /orgs/1/sites/site001
g, staffUser1027, staff001, /orgs/1/sites/site002
g, staffUser1027, staff001, /orgs/1/sites/site003
g, staffUser1027, staff001, /orgs/1/sites/site004
g, staffUser1027, staff001, /orgs/1/sites/site005
g, staffUser1028, staff001, /orgs/1/sites/site001
g, staffUser1028, staff001, /orgs/1/sites/site002
g, staffUser1028, staff001, /orgs/1/sites/site003
g, staffUser1028, staff001, /orgs/1/sites/site004
g, staffUser1028, staff001, /orgs/1/sites/site005
g, staffUser1029, staff001, /orgs/1/sites/site001
g, staffUser1029, staff001, /orgs/1/sites/site002
g, staffUser1029, staff001, /orgs/1/sites/site003
g, staffUser1029, staff001, /orgs/1/sites/site004
g, staffUser1029, staff001, /orgs/1/sites/site005
g, staffUser1030, staff001, /orgs/1/sites/site001
g, staffUser1030, staff001, /orgs/1/sites/site002
g, staffUser1030, staff001, /orgs/1/sites/site003
g, staffUser1030, staff001, /orgs/1/sites/site004
g, staffUser1030, staff001, /orgs/1/sites/site005
g, staffUser1031, staff001, /orgs/1/sites/site001
g, staffUser1031, staff001, /orgs/1/sites/site002
g, staffUser1031, staff001, /orgs/1/sites/site003
g, staffUser1031, staff001, /orgs/1/sites/site004
g, staffUser1031, staff001, /orgs/1/sites/site005
g, staffUser1032, staff001, /orgs/1/sites/site001
g, staffUser1032, staff001, /orgs/1/sites/site002
g, staffUser1032, staff001, /orgs/1/sites/site003
g, staffUser1032, staff001, /orgs/1/sites/site004
g, staffUser1032, staff001, /orgs/1/sites/site005
g, staffUser1033, staff001, /orgs/1/sites/site001
g, staffUser1033, staff001, /orgs/1/sites/site002
g, staffUser1033, staff001, /orgs/1/sites/site003
g, staffUser1033, staff001, /orgs/1/sites/site004
g, staffUser1033, staff001, /orgs/1/sites/site005
g, staffUser1034, staff001, /orgs/1/sites/site001
g, staffUser1034, staff001, /orgs/1/sites/site002
g, staffUser1034, staff001, /orgs/1/sites/site003
g, staffUser1034, staff001, /orgs/1/sites/site004
g, staffUser1034, staff001, /orgs/1/sites/site005
g, staffUser1035, staff001, /orgs/1/sites/site001
g, staffUser1035, staff001, /orgs/1/sites/site002
g, staffUser1035, staff001, /orgs/1/sites/site003
g, staffUser1035, staff001, /orgs/1/sites/site004
g, staffUser1035, staff001, /orgs/1/sites/site005
g, staffUser1036, staff001, /orgs/1/sites/site001
g, staffUser1036, staff001, /orgs/1/sites/site002
g, staffUser1036, staff001, /orgs/1/sites/site003
g, staffUser1036, staff001, /orgs/1/sites/site004
g, staffUser1036, staff001, /orgs/1/sites/site005
g, staffUser1037, staff001, /orgs/1/sites/site001
g, staffUser1037, staff001, /orgs/1/sites/site002
g, staffUser1037, staff001, /orgs/1/sites/site003
g, staffUser1037, staff001, /orgs/1/sites/site004
g, staffUser1037, staff001, /orgs/1/sites/site005
g, staffUser1038, staff001, /orgs/1/sites/site001
g, staffUser1038, staff001, /orgs/1/sites/site002
g, staffUser1038, staff001, /orgs/1/sites/site003
g, staffUser1038, staff001, /orgs/1/sites/site004
g, staffUser1038, staff001, /orgs/1/sites/site005
g, staffUser1039, staff001, /orgs/1/sites/site001
g, staffUser1039, staff001, /orgs/1/sites/site002
g, staffUser1039, staff001, /orgs/1/sites/site003
g, staffUser1039, staff001, /orgs/1/sites/site004
g, staffUser1039, staff001, /orgs/1/sites/site005
g, staffUser1040, staff001, /orgs/1/sites/site001
g, staffUser1040, staff001, /orgs/1/sites/site002
g, staffUser1040, staff001, /orgs/1/sites/site003
g, staffUser1040, staff001, /orgs/1/sites/site004
g, staffUser1040, staff001, /orgs/1/sites/site005
g, staffUser1041, staff001, /orgs/1/sites/site001
g, staffUser1041, staff001, /orgs/1/sites/site002
g, staffUser1041, staff001, /orgs/1/sites/site003
g, staffUser1041, staff001, /orgs/1/sites/site004
g, staffUser1041, staff001, /orgs/1/sites/site005
g, staffUser1042, staff001, /orgs/1/sites/site001
g, staffUser1042, staff001, /orgs/1/sites/site002
g, staffUser1042, staff001, /orgs/1/sites/site003
g, staffUser1042, staff001, /orgs/1/sites/site004
g, staffUser1042, staff001, /orgs/1/sites/site005
g, staffUser1043, staff001, /orgs/1/sites/site001
g, staffUser1043, staff001, /orgs/1/sites/site002
g, staffUser1043, staff001, /orgs/1/sites/site003
g, staffUser1043, staff001, /orgs/1/sites/site004
g, staffUser1043, staff001, /orgs/1/sites/site005
g, staffUser1044, staff001, /orgs/1/sites/site001
g, staffUser1044, staff001, /orgs/1/sites/site002
g, staffUser1044, staff001, /orgs/1/sites/site003
g, staffUser1044, staff001, /orgs/1/sites/site004
g, staffUser1044, staff001, /orgs/1/sites/site005
g, staffUser1045, staff001, /orgs/1/sites/site001
g, staffUser1045, staff001, /orgs/1/sites/site002
g, staffUser1045, staff001, /orgs/1/sites/site003
g, staffUser1045, staff001, /orgs/1/sites/site004
g, staffUser1045, staff001, /orgs/1/sites/site005
g, staffUser1046, staff001, /orgs/1/sites/site001
g, staffUser1046, staff001, /orgs/1/sites/site002
g, staffUser1046, staff001, /orgs/1/sites/site003
g, staffUser1046, staff001, /orgs/1/sites/site004
g, staffUser1046, staff001, /orgs/1/sites/site005
g, staffUser1047, staff001, /orgs/1/sites/site001
g, staffUser1047, staff001, /orgs/1/sites/site002
g, staffUser1047, staff001, /orgs/1/sites/site003
g, staffUser1047, staff001, /orgs/1/sites/site004
g, staffUser1047, staff001, /orgs/1/sites/site005
g, staffUser1048, staff001, /orgs/1/sites/site001
g, staffUser1048, staff001, /orgs/1/sites/site002
g, staffUser1048, staff001, /orgs/1/sites/site003
g, staffUser1048, staff001, /orgs/1/sites/site004
g, staffUser1048, staff001, /orgs/1/sites/site005
g, staffUser1049, staff001, /orgs/1/sites/site001
g, staffUser1049, staff001, /orgs/1/sites/site002
g, staffUser1049, staff001, /orgs/1/sites/site003
g, staffUser1049, staff001, /orgs/1/sites/site004
g, staffUser1049, staff001, /orgs/1/sites/site005
g, staffUser1050, staff001, /orgs/1/sites/site001
g, staffUser1050, staff001, /orgs/1/sites/site002
g, staffUser1050, staff001, /orgs/1/sites/site003
g, staffUser1050, staff001, /orgs/1/sites/site004
g, staffUser1050, staff001, /orgs/1/sites/site005
# Group - staff001, / org1
g, staffUser2001, staff001, /orgs/1/sites/site001
g, staffUser2001, staff001, /orgs/1/sites/site002
g, staffUser2001, staff001, /orgs/1/sites/site003
g, staffUser2001, staff001, /orgs/1/sites/site004
g, staffUser2001, staff001, /orgs/1/sites/site005
g, staffUser2001, staff001, /orgs/1/sites/site001
g, staffUser2001, staff001, /orgs/1/sites/site002
g, staffUser2001, staff001, /orgs/1/sites/site003
g, staffUser2001, staff001, /orgs/1/sites/site004
g, staffUser2001, staff001, /orgs/1/sites/site005
g, staffUser2003, staff001, /orgs/1/sites/site001
g, staffUser2003, staff001, /orgs/1/sites/site002
g, staffUser2003, staff001, /orgs/1/sites/site003
g, staffUser2003, staff001, /orgs/1/sites/site004
g, staffUser2003, staff001, /orgs/1/sites/site005
g, staffUser2004, staff001, /orgs/1/sites/site001
g, staffUser2004, staff001, /orgs/1/sites/site002
g, staffUser2004, staff001, /orgs/1/sites/site003
g, staffUser2004, staff001, /orgs/1/sites/site004
g, staffUser2004, staff001, /orgs/1/sites/site005
g, staffUser2005, staff001, /orgs/1/sites/site001
g, staffUser2005, staff001, /orgs/1/sites/site002
g, staffUser2005, staff001, /orgs/1/sites/site003
g, staffUser2005, staff001, /orgs/1/sites/site004
g, staffUser2005, staff001, /orgs/1/sites/site005
g, staffUser2006, staff001, /orgs/1/sites/site001
g, staffUser2006, staff001, /orgs/1/sites/site002
g, staffUser2006, staff001, /orgs/1/sites/site003
g, staffUser2006, staff001, /orgs/1/sites/site004
g, staffUser2006, staff001, /orgs/1/sites/site005
g, staffUser2007, staff001, /orgs/1/sites/site001
g, staffUser2007, staff001, /orgs/1/sites/site002
g, staffUser2007, staff001, /orgs/1/sites/site003
g, staffUser2007, staff001, /orgs/1/sites/site004
g, staffUser2007, staff001, /orgs/1/sites/site005
g, staffUser2008, staff001, /orgs/1/sites/site001
g, staffUser2008, staff001, /orgs/1/sites/site002
g, staffUser2008, staff001, /orgs/1/sites/site003
g, staffUser2008, staff001, /orgs/1/sites/site004
g, staffUser2008, staff001, /orgs/1/sites/site005
g, staffUser2009, staff001, /orgs/1/sites/site001
g, staffUser2009, staff001, /orgs/1/sites/site002
g, staffUser2009, staff001, /orgs/1/sites/site003
g, staffUser2009, staff001, /orgs/1/sites/site004
g, staffUser2009, staff001, /orgs/1/sites/site005
g, staffUser2010, staff001, /orgs/1/sites/site001
g, staffUser2010, staff001, /orgs/1/sites/site002
g, staffUser2010, staff001, /orgs/1/sites/site003
g, staffUser2010, staff001, /orgs/1/sites/site004
g, staffUser2010, staff001, /orgs/1/sites/site005
g, staffUser2011, staff001, /orgs/1/sites/site001
g, staffUser2011, staff001, /orgs/1/sites/site002
g, staffUser2011, staff001, /orgs/1/sites/site003
g, staffUser2011, staff001, /orgs/1/sites/site004
g, staffUser2011, staff001, /orgs/1/sites/site005
g, staffUser2012, staff001, /orgs/1/sites/site001
g, staffUser2012, staff001, /orgs/1/sites/site002
g, staffUser2012, staff001, /orgs/1/sites/site003
g, staffUser2012, staff001, /orgs/1/sites/site004
g, staffUser2012, staff001, /orgs/1/sites/site005
g, staffUser2013, staff001, /orgs/1/sites/site001
g, staffUser2013, staff001, /orgs/1/sites/site002
g, staffUser2013, staff001, /orgs/1/sites/site003
g, staffUser2013, staff001, /orgs/1/sites/site004
g, staffUser2013, staff001, /orgs/1/sites/site005
g, staffUser2014, staff001, /orgs/1/sites/site001
g, staffUser2014, staff001, /orgs/1/sites/site002
g, staffUser2014, staff001, /orgs/1/sites/site003
g, staffUser2014, staff001, /orgs/1/sites/site004
g, staffUser2014, staff001, /orgs/1/sites/site005
g, staffUser2015, staff001, /orgs/1/sites/site001
g, staffUser2015, staff001, /orgs/1/sites/site002
g, staffUser2015, staff001, /orgs/1/sites/site003
g, staffUser2015, staff001, /orgs/1/sites/site004
g, staffUser2015, staff001, /orgs/1/sites/site005
g, staffUser2016, staff001, /orgs/1/sites/site001
g, staffUser2016, staff001, /orgs/1/sites/site002
g, staffUser2016, staff001, /orgs/1/sites/site003
g, staffUser2016, staff001, /orgs/1/sites/site004
g, staffUser2016, staff001, /orgs/1/sites/site005
g, staffUser2017, staff001, /orgs/1/sites/site001
g, staffUser2017, staff001, /orgs/1/sites/site002
g, staffUser2017, staff001, /orgs/1/sites/site003
g, staffUser2017, staff001, /orgs/1/sites/site004
g, staffUser2017, staff001, /orgs/1/sites/site005
g, staffUser2018, staff001, /orgs/1/sites/site001
g, staffUser2018, staff001, /orgs/1/sites/site002
g, staffUser2018, staff001, /orgs/1/sites/site003
g, staffUser2018, staff001, /orgs/1/sites/site004
g, staffUser2018, staff001, /orgs/1/sites/site005
g, staffUser2019, staff001, /orgs/1/sites/site001
g, staffUser2019, staff001, /orgs/1/sites/site002
g, staffUser2019, staff001, /orgs/1/sites/site003
g, staffUser2019, staff001, /orgs/1/sites/site004
g, staffUser2019, staff001, /orgs/1/sites/site005
g, staffUser2020, staff001, /orgs/1/sites/site001
g, staffUser2020, staff001, /orgs/1/sites/site002
g, staffUser2020, staff001, /orgs/1/sites/site003
g, staffUser2020, staff001, /orgs/1/sites/site004
g, staffUser2020, staff001, /orgs/1/sites/site005
g, staffUser2021, staff001, /orgs/1/sites/site001
g, staffUser2021, staff001, /orgs/1/sites/site002
g, staffUser2021, staff001, /orgs/1/sites/site003
g, staffUser2021, staff001, /orgs/1/sites/site004
g, staffUser2021, staff001, /orgs/1/sites/site005
g, staffUser2022, staff001, /orgs/1/sites/site001
g, staffUser2022, staff001, /orgs/1/sites/site002
g, staffUser2022, staff001, /orgs/1/sites/site003
g, staffUser2022, staff001, /orgs/1/sites/site004
g, staffUser2022, staff001, /orgs/1/sites/site005
g, staffUser2023, staff001, /orgs/1/sites/site001
g, staffUser2023, staff001, /orgs/1/sites/site002
g, staffUser2023, staff001, /orgs/1/sites/site003
g, staffUser2023, staff001, /orgs/1/sites/site004
g, staffUser2023, staff001, /orgs/1/sites/site005
g, staffUser2024, staff001, /orgs/1/sites/site001
g, staffUser2024, staff001, /orgs/1/sites/site002
g, staffUser2024, staff001, /orgs/1/sites/site003
g, staffUser2024, staff001, /orgs/1/sites/site004
g, staffUser2024, staff001, /orgs/1/sites/site005
g, staffUser2025, staff001, /orgs/1/sites/site001
g, staffUser2025, staff001, /orgs/1/sites/site002
g, staffUser2025, staff001, /orgs/1/sites/site003
g, staffUser2025, staff001, /orgs/1/sites/site004
g, staffUser2025, staff001, /orgs/1/sites/site005
g, staffUser2026, staff001, /orgs/1/sites/site001
g, staffUser2026, staff001, /orgs/1/sites/site002
g, staffUser2026, staff001, /orgs/1/sites/site003
g, staffUser2026, staff001, /orgs/1/sites/site004
g, staffUser2026, staff001, /orgs/1/sites/site005
g, staffUser2027, staff001, /orgs/1/sites/site001
g, staffUser2027, staff001, /orgs/1/sites/site002
g, staffUser2027, staff001, /orgs/1/sites/site003
g, staffUser2027, staff001, /orgs/1/sites/site004
g, staffUser2027, staff001, /orgs/1/sites/site005
g, staffUser2028, staff001, /orgs/1/sites/site001
g, staffUser2028, staff001, /orgs/1/sites/site002
g, staffUser2028, staff001, /orgs/1/sites/site003
g, staffUser2028, staff001, /orgs/1/sites/site004
g, staffUser2028, staff001, /orgs/1/sites/site005
g, staffUser2029, staff001, /orgs/1/sites/site001
g, staffUser2029, staff001, /orgs/1/sites/site002
g, staffUser2029, staff001, /orgs/1/sites/site003
g, staffUser2029, staff001, /orgs/1/sites/site004
g, staffUser2029, staff001, /orgs/1/sites/site005
g, staffUser2030, staff001, /orgs/1/sites/site001
g, staffUser2030, staff001, /orgs/1/sites/site002
g, staffUser2030, staff001, /orgs/1/sites/site003
g, staffUser2030, staff001, /orgs/1/sites/site004
g, staffUser2030, staff001, /orgs/1/sites/site005
g, staffUser2031, staff001, /orgs/1/sites/site001
g, staffUser2031, staff001, /orgs/1/sites/site002
g, staffUser2031, staff001, /orgs/1/sites/site003
g, staffUser2031, staff001, /orgs/1/sites/site004
g, staffUser2031, staff001, /orgs/1/sites/site005
g, staffUser2032, staff001, /orgs/1/sites/site001
g, staffUser2032, staff001, /orgs/1/sites/site002
g, staffUser2032, staff001, /orgs/1/sites/site003
g, staffUser2032, staff001, /orgs/1/sites/site004
g, staffUser2032, staff001, /orgs/1/sites/site005
g, staffUser2033, staff001, /orgs/1/sites/site001
g, staffUser2033, staff001, /orgs/1/sites/site002
g, staffUser2033, staff001, /orgs/1/sites/site003
g, staffUser2033, staff001, /orgs/1/sites/site004
g, staffUser2033, staff001, /orgs/1/sites/site005
g, staffUser2034, staff001, /orgs/1/sites/site001
g, staffUser2034, staff001, /orgs/1/sites/site002
g, staffUser2034, staff001, /orgs/1/sites/site003
g, staffUser2034, staff001, /orgs/1/sites/site004
g, staffUser2034, staff001, /orgs/1/sites/site005
g, staffUser2035, staff001, /orgs/1/sites/site001
g, staffUser2035, staff001, /orgs/1/sites/site002
g, staffUser2035, staff001, /orgs/1/sites/site003
g, staffUser2035, staff001, /orgs/1/sites/site004
g, staffUser2035, staff001, /orgs/1/sites/site005
g, staffUser2036, staff001, /orgs/1/sites/site001
g, staffUser2036, staff001, /orgs/1/sites/site002
g, staffUser2036, staff001, /orgs/1/sites/site003
g, staffUser2036, staff001, /orgs/1/sites/site004
g, staffUser2036, staff001, /orgs/1/sites/site005
g, staffUser2037, staff001, /orgs/1/sites/site001
g, staffUser2037, staff001, /orgs/1/sites/site002
g, staffUser2037, staff001, /orgs/1/sites/site003
g, staffUser2037, staff001, /orgs/1/sites/site004
g, staffUser2037, staff001, /orgs/1/sites/site005
g, staffUser2038, staff001, /orgs/1/sites/site001
g, staffUser2038, staff001, /orgs/1/sites/site002
g, staffUser2038, staff001, /orgs/1/sites/site003
g, staffUser2038, staff001, /orgs/1/sites/site004
g, staffUser2038, staff001, /orgs/1/sites/site005
g, staffUser2039, staff001, /orgs/1/sites/site001
g, staffUser2039, staff001, /orgs/1/sites/site002
g, staffUser2039, staff001, /orgs/1/sites/site003
g, staffUser2039, staff001, /orgs/1/sites/site004
g, staffUser2039, staff001, /orgs/1/sites/site005
g, staffUser2040, staff001, /orgs/1/sites/site001
g, staffUser2040, staff001, /orgs/1/sites/site002
g, staffUser2040, staff001, /orgs/1/sites/site003
g, staffUser2040, staff001, /orgs/1/sites/site004
g, staffUser2040, staff001, /orgs/1/sites/site005
g, staffUser2041, staff001, /orgs/1/sites/site001
g, staffUser2041, staff001, /orgs/1/sites/site002
g, staffUser2041, staff001, /orgs/1/sites/site003
g, staffUser2041, staff001, /orgs/1/sites/site004
g, staffUser2041, staff001, /orgs/1/sites/site005
g, staffUser2042, staff001, /orgs/1/sites/site001
g, staffUser2042, staff001, /orgs/1/sites/site002
g, staffUser2042, staff001, /orgs/1/sites/site003
g, staffUser2042, staff001, /orgs/1/sites/site004
g, staffUser2042, staff001, /orgs/1/sites/site005
g, staffUser2043, staff001, /orgs/1/sites/site001
g, staffUser2043, staff001, /orgs/1/sites/site002
g, staffUser2043, staff001, /orgs/1/sites/site003
g, staffUser2043, staff001, /orgs/1/sites/site004
g, staffUser2043, staff001, /orgs/1/sites/site005
g, staffUser2044, staff001, /orgs/1/sites/site001
g, staffUser2044, staff001, /orgs/1/sites/site002
g, staffUser2044, staff001, /orgs/1/sites/site003
g, staffUser2044, staff001, /orgs/1/sites/site004
g, staffUser2044, staff001, /orgs/1/sites/site005
g, staffUser2045, staff001, /orgs/1/sites/site001
g, staffUser2045, staff001, /orgs/1/sites/site002
g, staffUser2045, staff001, /orgs/1/sites/site003
g, staffUser2045, staff001, /orgs/1/sites/site004
g, staffUser2045, staff001, /orgs/1/sites/site005
g, staffUser2046, staff001, /orgs/1/sites/site001
g, staffUser2046, staff001, /orgs/1/sites/site002
g, staffUser2046, staff001, /orgs/1/sites/site003
g, staffUser2046, staff001, /orgs/1/sites/site004
g, staffUser2046, staff001, /orgs/1/sites/site005
g, staffUser2047, staff001, /orgs/1/sites/site001
g, staffUser2047, staff001, /orgs/1/sites/site002
g, staffUser2047, staff001, /orgs/1/sites/site003
g, staffUser2047, staff001, /orgs/1/sites/site004
g, staffUser2047, staff001, /orgs/1/sites/site005
g, staffUser2048, staff001, /orgs/1/sites/site001
g, staffUser2048, staff001, /orgs/1/sites/site002
g, staffUser2048, staff001, /orgs/1/sites/site003
g, staffUser2048, staff001, /orgs/1/sites/site004
g, staffUser2048, staff001, /orgs/1/sites/site005
g, staffUser2049, staff001, /orgs/1/sites/site001
g, staffUser2049, staff001, /orgs/1/sites/site002
g, staffUser2049, staff001, /orgs/1/sites/site003
g, staffUser2049, staff001, /orgs/1/sites/site004
g, staffUser2049, staff001, /orgs/1/sites/site005
g, staffUser2050, staff001, /orgs/1/sites/site001
g, staffUser2050, staff001, /orgs/1/sites/site002
g, staffUser2050, staff001, /orgs/1/sites/site003
g, staffUser2050, staff001, /orgs/1/sites/site004
g, staffUser2050, staff001, /orgs/1/sites/site005
# Group - manager001, / org1
g, managerUser1001, manager001, /orgs/1/sites/site001
g, managerUser1001, manager001, /orgs/1/sites/site002
g, managerUser1001, manager001, /orgs/1/sites/site003
g, managerUser1001, manager001, /orgs/1/sites/site004
g, managerUser1001, manager001, /orgs/1/sites/site005
g, managerUser1001, manager001, /orgs/1/sites/site001
g, managerUser1001, manager001, /orgs/1/sites/site002
g, managerUser1001, manager001, /orgs/1/sites/site003
g, managerUser1001, manager001, /orgs/1/sites/site004
g, managerUser1001, manager001, /orgs/1/sites/site005
g, managerUser1003, manager001, /orgs/1/sites/site001
g, managerUser1003, manager001, /orgs/1/sites/site002
g, managerUser1003, manager001, /orgs/1/sites/site003
g, managerUser1003, manager001, /orgs/1/sites/site004
g, managerUser1003, manager001, /orgs/1/sites/site005
g, managerUser1004, manager001, /orgs/1/sites/site001
g, managerUser1004, manager001, /orgs/1/sites/site002
g, managerUser1004, manager001, /orgs/1/sites/site003
g, managerUser1004, manager001, /orgs/1/sites/site004
g, managerUser1004, manager001, /orgs/1/sites/site005
g, managerUser1005, manager001, /orgs/1/sites/site001
g, managerUser1005, manager001, /orgs/1/sites/site002
g, managerUser1005, manager001, /orgs/1/sites/site003
g, managerUser1005, manager001, /orgs/1/sites/site004
g, managerUser1005, manager001, /orgs/1/sites/site005
g, managerUser1006, manager001, /orgs/1/sites/site001
g, managerUser1006, manager001, /orgs/1/sites/site002
g, managerUser1006, manager001, /orgs/1/sites/site003
g, managerUser1006, manager001, /orgs/1/sites/site004
g, managerUser1006, manager001, /orgs/1/sites/site005
g, managerUser1007, manager001, /orgs/1/sites/site001
g, managerUser1007, manager001, /orgs/1/sites/site002
g, managerUser1007, manager001, /orgs/1/sites/site003
g, managerUser1007, manager001, /orgs/1/sites/site004
g, managerUser1007, manager001, /orgs/1/sites/site005
g, managerUser1008, manager001, /orgs/1/sites/site001
g, managerUser1008, manager001, /orgs/1/sites/site002
g, managerUser1008, manager001, /orgs/1/sites/site003
g, managerUser1008, manager001, /orgs/1/sites/site004
g, managerUser1008, manager001, /orgs/1/sites/site005
g, managerUser1009, manager001, /orgs/1/sites/site001
g, managerUser1009, manager001, /orgs/1/sites/site002
g, managerUser1009, manager001, /orgs/1/sites/site003
g, managerUser1009, manager001, /orgs/1/sites/site004
g, managerUser1009, manager001, /orgs/1/sites/site005
g, managerUser1010, manager001, /orgs/1/sites/site001
g, managerUser1010, manager001, /orgs/1/sites/site002
g, managerUser1010, manager001, /orgs/1/sites/site003
g, managerUser1010, manager001, /orgs/1/sites/site004
g, managerUser1010, manager001, /orgs/1/sites/site005
g, managerUser1011, manager001, /orgs/1/sites/site001
g, managerUser1011, manager001, /orgs/1/sites/site002
g, managerUser1011, manager001, /orgs/1/sites/site003
g, managerUser1011, manager001, /orgs/1/sites/site004
g, managerUser1011, manager001, /orgs/1/sites/site005
g, managerUser1012, manager001, /orgs/1/sites/site001
g, managerUser1012, manager001, /orgs/1/sites/site002
g, managerUser1012, manager001, /orgs/1/sites/site003
g, managerUser1012, manager001, /orgs/1/sites/site004
g, managerUser1012, manager001, /orgs/1/sites/site005
g, managerUser1013, manager001, /orgs/1/sites/site001
g, managerUser1013, manager001, /orgs/1/sites/site002
g, managerUser1013, manager001, /orgs/1/sites/site003
g, managerUser1013, manager001, /orgs/1/sites/site004
g, managerUser1013, manager001, /orgs/1/sites/site005
g, managerUser1014, manager001, /orgs/1/sites/site001
g, managerUser1014, manager001, /orgs/1/sites/site002
g, managerUser1014, manager001, /orgs/1/sites/site003
g, managerUser1014, manager001, /orgs/1/sites/site004
g, managerUser1014, manager001, /orgs/1/sites/site005
g, managerUser1015, manager001, /orgs/1/sites/site001
g, managerUser1015, manager001, /orgs/1/sites/site002
g, managerUser1015, manager001, /orgs/1/sites/site003
g, managerUser1015, manager001, /orgs/1/sites/site004
g, managerUser1015, manager001, /orgs/1/sites/site005
g, managerUser1016, manager001, /orgs/1/sites/site001
g, managerUser1016, manager001, /orgs/1/sites/site002
g, managerUser1016, manager001, /orgs/1/sites/site003
g, managerUser1016, manager001, /orgs/1/sites/site004
g, managerUser1016, manager001, /orgs/1/sites/site005
g, managerUser1017, manager001, /orgs/1/sites/site001
g, managerUser1017, manager001, /orgs/1/sites/site002
g, managerUser1017, manager001, /orgs/1/sites/site003
g, managerUser1017, manager001, /orgs/1/sites/site004
g, managerUser1017, manager001, /orgs/1/sites/site005
g, managerUser1018, manager001, /orgs/1/sites/site001
g, managerUser1018, manager001, /orgs/1/sites/site002
g, managerUser1018, manager001, /orgs/1/sites/site003
g, managerUser1018, manager001, /orgs/1/sites/site004
g, managerUser1018, manager001, /orgs/1/sites/site005
g, managerUser1019, manager001, /orgs/1/sites/site001
g, managerUser1019, manager001, /orgs/1/sites/site002
g, managerUser1019, manager001, /orgs/1/sites/site003
g, managerUser1019, manager001, /orgs/1/sites/site004
g, managerUser1019, manager001, /orgs/1/sites/site005
g, managerUser1020, manager001, /orgs/1/sites/site001
g, managerUser1020, manager001, /orgs/1/sites/site002
g, managerUser1020, manager001, /orgs/1/sites/site003
g, managerUser1020, manager001, /orgs/1/sites/site004
g, managerUser1020, manager001, /orgs/1/sites/site005
g, managerUser1021, manager001, /orgs/1/sites/site001
g, managerUser1021, manager001, /orgs/1/sites/site002
g, managerUser1021, manager001, /orgs/1/sites/site003
g, managerUser1021, manager001, /orgs/1/sites/site004
g, managerUser1021, manager001, /orgs/1/sites/site005
g, managerUser1022, manager001, /orgs/1/sites/site001
g, managerUser1022, manager001, /orgs/1/sites/site002
g, managerUser1022, manager001, /orgs/1/sites/site003
g, managerUser1022, manager001, /orgs/1/sites/site004
g, managerUser1022, manager001, /orgs/1/sites/site005
g, managerUser1023, manager001, /orgs/1/sites/site001
g, managerUser1023, manager001, /orgs/1/sites/site002
g, managerUser1023, manager001, /orgs/1/sites/site003
g, managerUser1023, manager001, /orgs/1/sites/site004
g, managerUser1023, manager001, /orgs/1/sites/site005
g, managerUser1024, manager001, /orgs/1/sites/site001
g, managerUser1024, manager001, /orgs/1/sites/site002
g, managerUser1024, manager001, /orgs/1/sites/site003
g, managerUser1024, manager001, /orgs/1/sites/site004
g, managerUser1024, manager001, /orgs/1/sites/site005
g, managerUser1025, manager001, /orgs/1/sites/site001
g, managerUser1025, manager001, /orgs/1/sites/site002
g, managerUser1025, manager001, /orgs/1/sites/site003
g, managerUser1025, manager001, /orgs/1/sites/site004
g, managerUser1025, manager001, /orgs/1/sites/site005
g, managerUser1026, manager001, /orgs/1/sites/site001
g, managerUser1026, manager001, /orgs/1/sites/site002
g, managerUser1026, manager001, /orgs/1/sites/site003
g, managerUser1026, manager001, /orgs/1/sites/site004
g, managerUser1026, manager001, /orgs/1/sites/site005
g, managerUser1027, manager001, /orgs/1/sites/site001
g, managerUser1027, manager001, /orgs/1/sites/site002
g, managerUser1027, manager001, /orgs/1/sites/site003
g, managerUser1027, manager001, /orgs/1/sites/site004
g, managerUser1027, manager001, /orgs/1/sites/site005
g, managerUser1028, manager001, /orgs/1/sites/site001
g, managerUser1028, manager001, /orgs/1/sites/site002
g, managerUser1028, manager001, /orgs/1/sites/site003
g, managerUser1028, manager001, /orgs/1/sites/site004
g, managerUser1028, manager001, /orgs/1/sites/site005
g, managerUser1029, manager001, /orgs/1/sites/site001
g, managerUser1029, manager001, /orgs/1/sites/site002
g, managerUser1029, manager001, /orgs/1/sites/site003
g, managerUser1029, manager001, /orgs/1/sites/site004
g, managerUser1029, manager001, /orgs/1/sites/site005
g, managerUser1030, manager001, /orgs/1/sites/site001
g, managerUser1030, manager001, /orgs/1/sites/site002
g, managerUser1030, manager001, /orgs/1/sites/site003
g, managerUser1030, manager001, /orgs/1/sites/site004
g, managerUser1030, manager001, /orgs/1/sites/site005
g, managerUser1031, manager001, /orgs/1/sites/site001
g, managerUser1031, manager001, /orgs/1/sites/site002
g, managerUser1031, manager001, /orgs/1/sites/site003
g, managerUser1031, manager001, /orgs/1/sites/site004
g, managerUser1031, manager001, /orgs/1/sites/site005
g, managerUser1032, manager001, /orgs/1/sites/site001
g, managerUser1032, manager001, /orgs/1/sites/site002
g, managerUser1032, manager001, /orgs/1/sites/site003
g, managerUser1032, manager001, /orgs/1/sites/site004
g, managerUser1032, manager001, /orgs/1/sites/site005
g, managerUser1033, manager001, /orgs/1/sites/site001
g, managerUser1033, manager001, /orgs/1/sites/site002
g, managerUser1033, manager001, /orgs/1/sites/site003
g, managerUser1033, manager001, /orgs/1/sites/site004
g, managerUser1033, manager001, /orgs/1/sites/site005
g, managerUser1034, manager001, /orgs/1/sites/site001
g, managerUser1034, manager001, /orgs/1/sites/site002
g, managerUser1034, manager001, /orgs/1/sites/site003
g, managerUser1034, manager001, /orgs/1/sites/site004
g, managerUser1034, manager001, /orgs/1/sites/site005
g, managerUser1035, manager001, /orgs/1/sites/site001
g, managerUser1035, manager001, /orgs/1/sites/site002
g, managerUser1035, manager001, /orgs/1/sites/site003
g, managerUser1035, manager001, /orgs/1/sites/site004
g, managerUser1035, manager001, /orgs/1/sites/site005
g, managerUser1036, manager001, /orgs/1/sites/site001
g, managerUser1036, manager001, /orgs/1/sites/site002
g, managerUser1036, manager001, /orgs/1/sites/site003
g, managerUser1036, manager001, /orgs/1/sites/site004
g, managerUser1036, manager001, /orgs/1/sites/site005
g, managerUser1037, manager001, /orgs/1/sites/site001
g, managerUser1037, manager001, /orgs/1/sites/site002
g, managerUser1037, manager001, /orgs/1/sites/site003
g, managerUser1037, manager001, /orgs/1/sites/site004
g, managerUser1037, manager001, /orgs/1/sites/site005
g, managerUser1038, manager001, /orgs/1/sites/site001
g, managerUser1038, manager001, /orgs/1/sites/site002
g, managerUser1038, manager001, /orgs/1/sites/site003
g, managerUser1038, manager001, /orgs/1/sites/site004
g, managerUser1038, manager001, /orgs/1/sites/site005
g, managerUser1039, manager001, /orgs/1/sites/site001
g, managerUser1039, manager001, /orgs/1/sites/site002
g, managerUser1039, manager001, /orgs/1/sites/site003
g, managerUser1039, manager001, /orgs/1/sites/site004
g, managerUser1039, manager001, /orgs/1/sites/site005
g, managerUser1040, manager001, /orgs/1/sites/site001
g, managerUser1040, manager001, /orgs/1/sites/site002
g, managerUser1040, manager001, /orgs/1/sites/site003
g, managerUser1040, manager001, /orgs/1/sites/site004
g, managerUser1040, manager001, /orgs/1/sites/site005
g, managerUser1041, manager001, /orgs/1/sites/site001
g, managerUser1041, manager001, /orgs/1/sites/site002
g, managerUser1041, manager001, /orgs/1/sites/site003
g, managerUser1041, manager001, /orgs/1/sites/site004
g, managerUser1041, manager001, /orgs/1/sites/site005
g, managerUser1042, manager001, /orgs/1/sites/site001
g, managerUser1042, manager001, /orgs/1/sites/site002
g, managerUser1042, manager001, /orgs/1/sites/site003
g, managerUser1042, manager001, /orgs/1/sites/site004
g, managerUser1042, manager001, /orgs/1/sites/site005
g, managerUser1043, manager001, /orgs/1/sites/site001
g, managerUser1043, manager001, /orgs/1/sites/site002
g, managerUser1043, manager001, /orgs/1/sites/site003
g, managerUser1043, manager001, /orgs/1/sites/site004
g, managerUser1043, manager001, /orgs/1/sites/site005
g, managerUser1044, manager001, /orgs/1/sites/site001
g, managerUser1044, manager001, /orgs/1/sites/site002
g, managerUser1044, manager001, /orgs/1/sites/site003
g, managerUser1044, manager001, /orgs/1/sites/site004
g, managerUser1044, manager001, /orgs/1/sites/site005
g, managerUser1045, manager001, /orgs/1/sites/site001
g, managerUser1045, manager001, /orgs/1/sites/site002
g, managerUser1045, manager001, /orgs/1/sites/site003
g, managerUser1045, manager001, /orgs/1/sites/site004
g, managerUser1045, manager001, /orgs/1/sites/site005
g, managerUser1046, manager001, /orgs/1/sites/site001
g, managerUser1046, manager001, /orgs/1/sites/site002
g, managerUser1046, manager001, /orgs/1/sites/site003
g, managerUser1046, manager001, /orgs/1/sites/site004
g, managerUser1046, manager001, /orgs/1/sites/site005
g, managerUser1047, manager001, /orgs/1/sites/site001
g, managerUser1047, manager001, /orgs/1/sites/site002
g, managerUser1047, manager001, /orgs/1/sites/site003
g, managerUser1047, manager001, /orgs/1/sites/site004
g, managerUser1047, manager001, /orgs/1/sites/site005
g, managerUser1048, manager001, /orgs/1/sites/site001
g, managerUser1048, manager001, /orgs/1/sites/site002
g, managerUser1048, manager001, /orgs/1/sites/site003
g, managerUser1048, manager001, /orgs/1/sites/site004
g, managerUser1048, manager001, /orgs/1/sites/site005
g, managerUser1049, manager001, /orgs/1/sites/site001
g, managerUser1049, manager001, /orgs/1/sites/site002
g, managerUser1049, manager001, /orgs/1/sites/site003
g, managerUser1049, manager001, /orgs/1/sites/site004
g, managerUser1049, manager001, /orgs/1/sites/site005
g, managerUser1050, manager001, /orgs/1/sites/site001
g, managerUser1050, manager001, /orgs/1/sites/site002
g, managerUser1050, manager001, /orgs/1/sites/site003
g, managerUser1050, manager001, /orgs/1/sites/site004
g, managerUser1050, manager001, /orgs/1/sites/site005
# Group - manager001, / org1
g, managerUser2001, manager001, /orgs/1/sites/site001
g, managerUser2001, manager001, /orgs/1/sites/site002
g, managerUser2001, manager001, /orgs/1/sites/site003
g, managerUser2001, manager001, /orgs/1/sites/site004
g, managerUser2001, manager001, /orgs/1/sites/site005
g, managerUser2001, manager001, /orgs/1/sites/site001
g, managerUser2001, manager001, /orgs/1/sites/site002
g, managerUser2001, manager001, /orgs/1/sites/site003
g, managerUser2001, manager001, /orgs/1/sites/site004
g, managerUser2001, manager001, /orgs/1/sites/site005
g, managerUser2003, manager001, /orgs/1/sites/site001
g, managerUser2003, manager001, /orgs/1/sites/site002
g, managerUser2003, manager001, /orgs/1/sites/site003
g, managerUser2003, manager001, /orgs/1/sites/site004
g, managerUser2003, manager001, /orgs/1/sites/site005
g, managerUser2004, manager001, /orgs/1/sites/site001
g, managerUser2004, manager001, /orgs/1/sites/site002
g, managerUser2004, manager001, /orgs/1/sites/site003
g, managerUser2004, manager001, /orgs/1/sites/site004
g, managerUser2004, manager001, /orgs/1/sites/site005
g, managerUser2005, manager001, /orgs/1/sites/site001
g, managerUser2005, manager001, /orgs/1/sites/site002
g, managerUser2005, manager001, /orgs/1/sites/site003
g, managerUser2005, manager001, /orgs/1/sites/site004
g, managerUser2005, manager001, /orgs/1/sites/site005
g, managerUser2006, manager001, /orgs/1/sites/site001
g, managerUser2006, manager001, /orgs/1/sites/site002
g, managerUser2006, manager001, /orgs/1/sites/site003
g, managerUser2006, manager001, /orgs/1/sites/site004
g, managerUser2006, manager001, /orgs/1/sites/site005
g, managerUser2007, manager001, /orgs/1/sites/site001
g, managerUser2007, manager001, /orgs/1/sites/site002
g, managerUser2007, manager001, /orgs/1/sites/site003
g, managerUser2007, manager001, /orgs/1/sites/site004
g, managerUser2007, manager001, /orgs/1/sites/site005
g, managerUser2008, manager001, /orgs/1/sites/site001
g, managerUser2008, manager001, /orgs/1/sites/site002
g, managerUser2008, manager001, /orgs/1/sites/site003
g, managerUser2008, manager001, /orgs/1/sites/site004
g, managerUser2008, manager001, /orgs/1/sites/site005
g, managerUser2009, manager001, /orgs/1/sites/site001
g, managerUser2009, manager001, /orgs/1/sites/site002
g, managerUser2009, manager001, /orgs/1/sites/site003
g, managerUser2009, manager001, /orgs/1/sites/site004
g, managerUser2009, manager001, /orgs/1/sites/site005
g, managerUser2010, manager001, /orgs/1/sites/site001
g, managerUser2010, manager001, /orgs/1/sites/site002
g, managerUser2010, manager001, /orgs/1/sites/site003
g, managerUser2010, manager001, /orgs/1/sites/site004
g, managerUser2010, manager001, /orgs/1/sites/site005
g, managerUser2011, manager001, /orgs/1/sites/site001
g, managerUser2011, manager001, /orgs/1/sites/site002
g, managerUser2011, manager001, /orgs/1/sites/site003
g, managerUser2011, manager001, /orgs/1/sites/site004
g, managerUser2011, manager001, /orgs/1/sites/site005
g, managerUser2012, manager001, /orgs/1/sites/site001
g, managerUser2012, manager001, /orgs/1/sites/site002
g, managerUser2012, manager001, /orgs/1/sites/site003
g, managerUser2012, manager001, /orgs/1/sites/site004
g, managerUser2012, manager001, /orgs/1/sites/site005
g, managerUser2013, manager001, /orgs/1/sites/site001
g, managerUser2013, manager001, /orgs/1/sites/site002
g, managerUser2013, manager001, /orgs/1/sites/site003
g, managerUser2013, manager001, /orgs/1/sites/site004
g, managerUser2013, manager001, /orgs/1/sites/site005
g, managerUser2014, manager001, /orgs/1/sites/site001
g, managerUser2014, manager001, /orgs/1/sites/site002
g, managerUser2014, manager001, /orgs/1/sites/site003
g, managerUser2014, manager001, /orgs/1/sites/site004
g, managerUser2014, manager001, /orgs/1/sites/site005
g, managerUser2015, manager001, /orgs/1/sites/site001
g, managerUser2015, manager001, /orgs/1/sites/site002
g, managerUser2015, manager001, /orgs/1/sites/site003
g, managerUser2015, manager001, /orgs/1/sites/site004
g, managerUser2015, manager001, /orgs/1/sites/site005
g, managerUser2016, manager001, /orgs/1/sites/site001
g, managerUser2016, manager001, /orgs/1/sites/site002
g, managerUser2016, manager001, /orgs/1/sites/site003
g, managerUser2016, manager001, /orgs/1/sites/site004
g, managerUser2016, manager001, /orgs/1/sites/site005
g, managerUser2017, manager001, /orgs/1/sites/site001
g, managerUser2017, manager001, /orgs/1/sites/site002
g, managerUser2017, manager001, /orgs/1/sites/site003
g, managerUser2017, manager001, /orgs/1/sites/site004
g, managerUser2017, manager001, /orgs/1/sites/site005
g, managerUser2018, manager001, /orgs/1/sites/site001
g, managerUser2018, manager001, /orgs/1/sites/site002
g, managerUser2018, manager001, /orgs/1/sites/site003
g, managerUser2018, manager001, /orgs/1/sites/site004
g, managerUser2018, manager001, /orgs/1/sites/site005
g, managerUser2019, manager001, /orgs/1/sites/site001
g, managerUser2019, manager001, /orgs/1/sites/site002
g, managerUser2019, manager001, /orgs/1/sites/site003
g, managerUser2019, manager001, /orgs/1/sites/site004
g, managerUser2019, manager001, /orgs/1/sites/site005
g, managerUser2020, manager001, /orgs/1/sites/site001
g, managerUser2020, manager001, /orgs/1/sites/site002
g, managerUser2020, manager001, /orgs/1/sites/site003
g, managerUser2020, manager001, /orgs/1/sites/site004
g, managerUser2020, manager001, /orgs/1/sites/site005
g, managerUser2021, manager001, /orgs/1/sites/site001
g, managerUser2021, manager001, /orgs/1/sites/site002
g, managerUser2021, manager001, /orgs/1/sites/site003
g, managerUser2021, manager001, /orgs/1/sites/site004
g, managerUser2021, manager001, /orgs/1/sites/site005
g, managerUser2022, manager001, /orgs/1/sites/site001
g, managerUser2022, manager001, /orgs/1/sites/site002
g, managerUser2022, manager001, /orgs/1/sites/site003
g, managerUser2022, manager001, /orgs/1/sites/site004
g, managerUser2022, manager001, /orgs/1/sites/site005
g, managerUser2023, manager001, /orgs/1/sites/site001
g, managerUser2023, manager001, /orgs/1/sites/site002
g, managerUser2023, manager001, /orgs/1/sites/site003
g, managerUser2023, manager001, /orgs/1/sites/site004
g, managerUser2023, manager001, /orgs/1/sites/site005
g, managerUser2024, manager001, /orgs/1/sites/site001
g, managerUser2024, manager001, /orgs/1/sites/site002
g, managerUser2024, manager001, /orgs/1/sites/site003
g, managerUser2024, manager001, /orgs/1/sites/site004
g, managerUser2024, manager001, /orgs/1/sites/site005
g, managerUser2025, manager001, /orgs/1/sites/site001
g, managerUser2025, manager001, /orgs/1/sites/site002
g, managerUser2025, manager001, /orgs/1/sites/site003
g, managerUser2025, manager001, /orgs/1/sites/site004
g, managerUser2025, manager001, /orgs/1/sites/site005
g, managerUser2026, manager001, /orgs/1/sites/site001
g, managerUser2026, manager001, /orgs/1/sites/site002
g, managerUser2026, manager001, /orgs/1/sites/site003
g, managerUser2026, manager001, /orgs/1/sites/site004
g, managerUser2026, manager001, /orgs/1/sites/site005
g, managerUser2027, manager001, /orgs/1/sites/site001
g, managerUser2027, manager001, /orgs/1/sites/site002
g, managerUser2027, manager001, /orgs/1/sites/site003
g, managerUser2027, manager001, /orgs/1/sites/site004
g, managerUser2027, manager001, /orgs/1/sites/site005
g, managerUser2028, manager001, /orgs/1/sites/site001
g, managerUser2028, manager001, /orgs/1/sites/site002
g, managerUser2028, manager001, /orgs/1/sites/site003
g, managerUser2028, manager001, /orgs/1/sites/site004
g, managerUser2028, manager001, /orgs/1/sites/site005
g, managerUser2029, manager001, /orgs/1/sites/site001
g, managerUser2029, manager001, /orgs/1/sites/site002
g, managerUser2029, manager001, /orgs/1/sites/site003
g, managerUser2029, manager001, /orgs/1/sites/site004
g, managerUser2029, manager001, /orgs/1/sites/site005
g, managerUser2030, manager001, /orgs/1/sites/site001
g, managerUser2030, manager001, /orgs/1/sites/site002
g, managerUser2030, manager001, /orgs/1/sites/site003
g, managerUser2030, manager001, /orgs/1/sites/site004
g, managerUser2030, manager001, /orgs/1/sites/site005
g, managerUser2031, manager001, /orgs/1/sites/site001
g, managerUser2031, manager001, /orgs/1/sites/site002
g, managerUser2031, manager001, /orgs/1/sites/site003
g, managerUser2031, manager001, /orgs/1/sites/site004
g, managerUser2031, manager001, /orgs/1/sites/site005
g, managerUser2032, manager001, /orgs/1/sites/site001
g, managerUser2032, manager001, /orgs/1/sites/site002
g, managerUser2032, manager001, /orgs/1/sites/site003
g, managerUser2032, manager001, /orgs/1/sites/site004
g, managerUser2032, manager001, /orgs/1/sites/site005
g, managerUser2033, manager001, /orgs/1/sites/site001
g, managerUser2033, manager001, /orgs/1/sites/site002
g, managerUser2033, manager001, /orgs/1/sites/site003
g, managerUser2033, manager001, /orgs/1/sites/site004
g, managerUser2033, manager001, /orgs/1/sites/site005
g, managerUser2034, manager001, /orgs/1/sites/site001
g, managerUser2034, manager001, /orgs/1/sites/site002
g, managerUser2034, manager001, /orgs/1/sites/site003
g, managerUser2034, manager001, /orgs/1/sites/site004
g, managerUser2034, manager001, /orgs/1/sites/site005
g, managerUser2035, manager001, /orgs/1/sites/site001
g, managerUser2035, manager001, /orgs/1/sites/site002
g, managerUser2035, manager001, /orgs/1/sites/site003
g, managerUser2035, manager001, /orgs/1/sites/site004
g, managerUser2035, manager001, /orgs/1/sites/site005
g, managerUser2036, manager001, /orgs/1/sites/site001
g, managerUser2036, manager001, /orgs/1/sites/site002
g, managerUser2036, manager001, /orgs/1/sites/site003
g, managerUser2036, manager001, /orgs/1/sites/site004
g, managerUser2036, manager001, /orgs/1/sites/site005
g, managerUser2037, manager001, /orgs/1/sites/site001
g, managerUser2037, manager001, /orgs/1/sites/site002
g, managerUser2037, manager001, /orgs/1/sites/site003
g, managerUser2037, manager001, /orgs/1/sites/site004
g, managerUser2037, manager001, /orgs/1/sites/site005
g, managerUser2038, manager001, /orgs/1/sites/site001
g, managerUser2038, manager001, /orgs/1/sites/site002
g, managerUser2038, manager001, /orgs/1/sites/site003
g, managerUser2038, manager001, /orgs/1/sites/site004
g, managerUser2038, manager001, /orgs/1/sites/site005
g, managerUser2039, manager001, /orgs/1/sites/site001
g, managerUser2039, manager001, /orgs/1/sites/site002
g, managerUser2039, manager001, /orgs/1/sites/site003
g, managerUser2039, manager001, /orgs/1/sites/site004
g, managerUser2039, manager001, /orgs/1/sites/site005
g, managerUser2040, manager001, /orgs/1/sites/site001
g, managerUser2040, manager001, /orgs/1/sites/site002
g, managerUser2040, manager001, /orgs/1/sites/site003
g, managerUser2040, manager001, /orgs/1/sites/site004
g, managerUser2040, manager001, /orgs/1/sites/site005
g, managerUser2041, manager001, /orgs/1/sites/site001
g, managerUser2041, manager001, /orgs/1/sites/site002
g, managerUser2041, manager001, /orgs/1/sites/site003
g, managerUser2041, manager001, /orgs/1/sites/site004
g, managerUser2041, manager001, /orgs/1/sites/site005
g, managerUser2042, manager001, /orgs/1/sites/site001
g, managerUser2042, manager001, /orgs/1/sites/site002
g, managerUser2042, manager001, /orgs/1/sites/site003
g, managerUser2042, manager001, /orgs/1/sites/site004
g, managerUser2042, manager001, /orgs/1/sites/site005
g, managerUser2043, manager001, /orgs/1/sites/site001
g, managerUser2043, manager001, /orgs/1/sites/site002
g, managerUser2043, manager001, /orgs/1/sites/site003
g, managerUser2043, manager001, /orgs/1/sites/site004
g, managerUser2043, manager001, /orgs/1/sites/site005
g, managerUser2044, manager001, /orgs/1/sites/site001
g, managerUser2044, manager001, /orgs/1/sites/site002
g, managerUser2044, manager001, /orgs/1/sites/site003
g, managerUser2044, manager001, /orgs/1/sites/site004
g, managerUser2044, manager001, /orgs/1/sites/site005
g, managerUser2045, manager001, /orgs/1/sites/site001
g, managerUser2045, manager001, /orgs/1/sites/site002
g, managerUser2045, manager001, /orgs/1/sites/site003
g, managerUser2045, manager001, /orgs/1/sites/site004
g, managerUser2045, manager001, /orgs/1/sites/site005
g, managerUser2046, manager001, /orgs/1/sites/site001
g, managerUser2046, manager001, /orgs/1/sites/site002
g, managerUser2046, manager001, /orgs/1/sites/site003
g, managerUser2046, manager001, /orgs/1/sites/site004
g, managerUser2046, manager001, /orgs/1/sites/site005
g, managerUser2047, manager001, /orgs/1/sites/site001
g, managerUser2047, manager001, /orgs/1/sites/site002
g, managerUser2047, manager001, /orgs/1/sites/site003
g, managerUser2047, manager001, /orgs/1/sites/site004
g, managerUser2047, manager001, /orgs/1/sites/site005
g, managerUser2048, manager001, /orgs/1/sites/site001
g, managerUser2048, manager001, /orgs/1/sites/site002
g, managerUser2048, manager001, /orgs/1/sites/site003
g, managerUser2048, manager001, /orgs/1/sites/site004
g, managerUser2048, manager001, /orgs/1/sites/site005
g, managerUser2049, manager001, /orgs/1/sites/site001
g, managerUser2049, manager001, /orgs/1/sites/site002
g, managerUser2049, manager001, /orgs/1/sites/site003
g, managerUser2049, manager001, /orgs/1/sites/site004
g, managerUser2049, manager001, /orgs/1/sites/site005
g, managerUser2050, manager001, /orgs/1/sites/site001
g, managerUser2050, manager001, /orgs/1/sites/site002
g, managerUser2050, manager001, /orgs/1/sites/site003
g, managerUser2050, manager001, /orgs/1/sites/site004
g, managerUser2050, manager001, /orgs/1/sites/site005
# Group - customer001, / org1
g, customerUser1001, customer001, /orgs/1/sites/site001
g, customerUser1001, customer001, /orgs/1/sites/site002
g, customerUser1001, customer001, /orgs/1/sites/site003
g, customerUser1001, customer001, /orgs/1/sites/site004
g, customerUser1001, customer001, /orgs/1/sites/site005
g, customerUser1001, customer001, /orgs/1/sites/site001
g, customerUser1001, customer001, /orgs/1/sites/site002
g, customerUser1001, customer001, /orgs/1/sites/site003
g, customerUser1001, customer001, /orgs/1/sites/site004
g, customerUser1001, customer001, /orgs/1/sites/site005
g, customerUser1003, customer001, /orgs/1/sites/site001
g, customerUser1003, customer001, /orgs/1/sites/site002
g, customerUser1003, customer001, /orgs/1/sites/site003
g, customerUser1003, customer001, /orgs/1/sites/site004
g, customerUser1003, customer001, /orgs/1/sites/site005
g, customerUser1004, customer001, /orgs/1/sites/site001
g, customerUser1004, customer001, /orgs/1/sites/site002
g, customerUser1004, customer001, /orgs/1/sites/site003
g, customerUser1004, customer001, /orgs/1/sites/site004
g, customerUser1004, customer001, /orgs/1/sites/site005
g, customerUser1005, customer001, /orgs/1/sites/site001
g, customerUser1005, customer001, /orgs/1/sites/site002
g, customerUser1005, customer001, /orgs/1/sites/site003
g, customerUser1005, customer001, /orgs/1/sites/site004
g, customerUser1005, customer001, /orgs/1/sites/site005
g, customerUser1006, customer001, /orgs/1/sites/site001
g, customerUser1006, customer001, /orgs/1/sites/site002
g, customerUser1006, customer001, /orgs/1/sites/site003
g, customerUser1006, customer001, /orgs/1/sites/site004
g, customerUser1006, customer001, /orgs/1/sites/site005
g, customerUser1007, customer001, /orgs/1/sites/site001
g, customerUser1007, customer001, /orgs/1/sites/site002
g, customerUser1007, customer001, /orgs/1/sites/site003
g, customerUser1007, customer001, /orgs/1/sites/site004
g, customerUser1007, customer001, /orgs/1/sites/site005
g, customerUser1008, customer001, /orgs/1/sites/site001
g, customerUser1008, customer001, /orgs/1/sites/site002
g, customerUser1008, customer001, /orgs/1/sites/site003
g, customerUser1008, customer001, /orgs/1/sites/site004
g, customerUser1008, customer001, /orgs/1/sites/site005
g, customerUser1009, customer001, /orgs/1/sites/site001
g, customerUser1009, customer001, /orgs/1/sites/site002
g, customerUser1009, customer001, /orgs/1/sites/site003
g, customerUser1009, customer001, /orgs/1/sites/site004
g, customerUser1009, customer001, /orgs/1/sites/site005
g, customerUser1010, customer001, /orgs/1/sites/site001
g, customerUser1010, customer001, /orgs/1/sites/site002
g, customerUser1010, customer001, /orgs/1/sites/site003
g, customerUser1010, customer001, /orgs/1/sites/site004
g, customerUser1010, customer001, /orgs/1/sites/site005
g, customerUser1011, customer001, /orgs/1/sites/site001
g, customerUser1011, customer001, /orgs/1/sites/site002
g, customerUser1011, customer001, /orgs/1/sites/site003
g, customerUser1011, customer001, /orgs/1/sites/site004
g, customerUser1011, customer001, /orgs/1/sites/site005
g, customerUser1012, customer001, /orgs/1/sites/site001
g, customerUser1012, customer001, /orgs/1/sites/site002
g, customerUser1012, customer001, /orgs/1/sites/site003
g, customerUser1012, customer001, /orgs/1/sites/site004
g, customerUser1012, customer001, /orgs/1/sites/site005
g, customerUser1013, customer001, /orgs/1/sites/site001
g, customerUser1013, customer001, /orgs/1/sites/site002
g, customerUser1013, customer001, /orgs/1/sites/site003
g, customerUser1013, customer001, /orgs/1/sites/site004
g, customerUser1013, customer001, /orgs/1/sites/site005
g, customerUser1014, customer001, /orgs/1/sites/site001
g, customerUser1014, customer001, /orgs/1/sites/site002
g, customerUser1014, customer001, /orgs/1/sites/site003
g, customerUser1014, customer001, /orgs/1/sites/site004
g, customerUser1014, customer001, /orgs/1/sites/site005
g, customerUser1015, customer001, /orgs/1/sites/site001
g, customerUser1015, customer001, /orgs/1/sites/site002
g, customerUser1015, customer001, /orgs/1/sites/site003
g, customerUser1015, customer001, /orgs/1/sites/site004
g, customerUser1015, customer001, /orgs/1/sites/site005
g, customerUser1016, customer001, /orgs/1/sites/site001
g, customerUser1016, customer001, /orgs/1/sites/site002
g, customerUser1016, customer001, /orgs/1/sites/site003
g, customerUser1016, customer001, /orgs/1/sites/site004
g, customerUser1016, customer001, /orgs/1/sites/site005
g, customerUser1017, customer001, /orgs/1/sites/site001
g, customerUser1017, customer001, /orgs/1/sites/site002
g, customerUser1017, customer001, /orgs/1/sites/site003
g, customerUser1017, customer001, /orgs/1/sites/site004
g, customerUser1017, customer001, /orgs/1/sites/site005
g, customerUser1018, customer001, /orgs/1/sites/site001
g, customerUser1018, customer001, /orgs/1/sites/site002
g, customerUser1018, customer001, /orgs/1/sites/site003
g, customerUser1018, customer001, /orgs/1/sites/site004
g, customerUser1018, customer001, /orgs/1/sites/site005
g, customerUser1019, customer001, /orgs/1/sites/site001
g, customerUser1019, customer001, /orgs/1/sites/site002
g, customerUser1019, customer001, /orgs/1/sites/site003
g, customerUser1019, customer001, /orgs/1/sites/site004
g, customerUser1019, customer001, /orgs/1/sites/site005
g, customerUser1020, customer001, /orgs/1/sites/site001
g, customerUser1020, customer001, /orgs/1/sites/site002
g, customerUser1020, customer001, /orgs/1/sites/site003
g, customerUser1020, customer001, /orgs/1/sites/site004
g, customerUser1020, customer001, /orgs/1/sites/site005
g, customerUser1021, customer001, /orgs/1/sites/site001
g, customerUser1021, customer001, /orgs/1/sites/site002
g, customerUser1021, customer001, /orgs/1/sites/site003
g, customerUser1021, customer001, /orgs/1/sites/site004
g, customerUser1021, customer001, /orgs/1/sites/site005
g, customerUser1022, customer001, /orgs/1/sites/site001
g, customerUser1022, customer001, /orgs/1/sites/site002
g, customerUser1022, customer001, /orgs/1/sites/site003
g, customerUser1022, customer001, /orgs/1/sites/site004
g, customerUser1022, customer001, /orgs/1/sites/site005
g, customerUser1023, customer001, /orgs/1/sites/site001
g, customerUser1023, customer001, /orgs/1/sites/site002
g, customerUser1023, customer001, /orgs/1/sites/site003
g, customerUser1023, customer001, /orgs/1/sites/site004
g, customerUser1023, customer001, /orgs/1/sites/site005
g, customerUser1024, customer001, /orgs/1/sites/site001
g, customerUser1024, customer001, /orgs/1/sites/site002
g, customerUser1024, customer001, /orgs/1/sites/site003
g, customerUser1024, customer001, /orgs/1/sites/site004
g, customerUser1024, customer001, /orgs/1/sites/site005
g, customerUser1025, customer001, /orgs/1/sites/site001
g, customerUser1025, customer001, /orgs/1/sites/site002
g, customerUser1025, customer001, /orgs/1/sites/site003
g, customerUser1025, customer001, /orgs/1/sites/site004
g, customerUser1025, customer001, /orgs/1/sites/site005
g, customerUser1026, customer001, /orgs/1/sites/site001
g, customerUser1026, customer001, /orgs/1/sites/site002
g, customerUser1026, customer001, /orgs/1/sites/site003
g, customerUser1026, customer001, /orgs/1/sites/site004
g, customerUser1026, customer001, /orgs/1/sites/site005
g, customerUser1027, customer001, /orgs/1/sites/site001
g, customerUser1027, customer001, /orgs/1/sites/site002
g, customerUser1027, customer001, /orgs/1/sites/site003
g, customerUser1027, customer001, /orgs/1/sites/site004
g, customerUser1027, customer001, /orgs/1/sites/site005
g, customerUser1028, customer001, /orgs/1/sites/site001
g, customerUser1028, customer001, /orgs/1/sites/site002
g, customerUser1028, customer001, /orgs/1/sites/site003
g, customerUser1028, customer001, /orgs/1/sites/site004
g, customerUser1028, customer001, /orgs/1/sites/site005
g, customerUser1029, customer001, /orgs/1/sites/site001
g, customerUser1029, customer001, /orgs/1/sites/site002
g, customerUser1029, customer001, /orgs/1/sites/site003
g, customerUser1029, customer001, /orgs/1/sites/site004
g, customerUser1029, customer001, /orgs/1/sites/site005
g, customerUser1030, customer001, /orgs/1/sites/site001
g, customerUser1030, customer001, /orgs/1/sites/site002
g, customerUser1030, customer001, /orgs/1/sites/site003
g, customerUser1030, customer001, /orgs/1/sites/site004
g, customerUser1030, customer001, /orgs/1/sites/site005
g, customerUser1031, customer001, /orgs/1/sites/site001
g, customerUser1031, customer001, /orgs/1/sites/site002
g, customerUser1031, customer001, /orgs/1/sites/site003
g, customerUser1031, customer001, /orgs/1/sites/site004
g, customerUser1031, customer001, /orgs/1/sites/site005
g, customerUser1032, customer001, /orgs/1/sites/site001
g, customerUser1032, customer001, /orgs/1/sites/site002
g, customerUser1032, customer001, /orgs/1/sites/site003
g, customerUser1032, customer001, /orgs/1/sites/site004
g, customerUser1032, customer001, /orgs/1/sites/site005
g, customerUser1033, customer001, /orgs/1/sites/site001
g, customerUser1033, customer001, /orgs/1/sites/site002
g, customerUser1033, customer001, /orgs/1/sites/site003
g, customerUser1033, customer001, /orgs/1/sites/site004
g, customerUser1033, customer001, /orgs/1/sites/site005
g, customerUser1034, customer001, /orgs/1/sites/site001
g, customerUser1034, customer001, /orgs/1/sites/site002
g, customerUser1034, customer001, /orgs/1/sites/site003
g, customerUser1034, customer001, /orgs/1/sites/site004
g, customerUser1034, customer001, /orgs/1/sites/site005
g, customerUser1035, customer001, /orgs/1/sites/site001
g, customerUser1035, customer001, /orgs/1/sites/site002
g, customerUser1035, customer001, /orgs/1/sites/site003
g, customerUser1035, customer001, /orgs/1/sites/site004
g, customerUser1035, customer001, /orgs/1/sites/site005
g, customerUser1036, customer001, /orgs/1/sites/site001
g, customerUser1036, customer001, /orgs/1/sites/site002
g, customerUser1036, customer001, /orgs/1/sites/site003
g, customerUser1036, customer001, /orgs/1/sites/site004
g, customerUser1036, customer001, /orgs/1/sites/site005
g, customerUser1037, customer001, /orgs/1/sites/site001
g, customerUser1037, customer001, /orgs/1/sites/site002
g, customerUser1037, customer001, /orgs/1/sites/site003
g, customerUser1037, customer001, /orgs/1/sites/site004
g, customerUser1037, customer001, /orgs/1/sites/site005
g, customerUser1038, customer001, /orgs/1/sites/site001
g, customerUser1038, customer001, /orgs/1/sites/site002
g, customerUser1038, customer001, /orgs/1/sites/site003
g, customerUser1038, customer001, /orgs/1/sites/site004
g, customerUser1038, customer001, /orgs/1/sites/site005
g, customerUser1039, customer001, /orgs/1/sites/site001
g, customerUser1039, customer001, /orgs/1/sites/site002
g, customerUser1039, customer001, /orgs/1/sites/site003
g, customerUser1039, customer001, /orgs/1/sites/site004
g, customerUser1039, customer001, /orgs/1/sites/site005
g, customerUser1040, customer001, /orgs/1/sites/site001
g, customerUser1040, customer001, /orgs/1/sites/site002
g, customerUser1040, customer001, /orgs/1/sites/site003
g, customerUser1040, customer001, /orgs/1/sites/site004
g, customerUser1040, customer001, /orgs/1/sites/site005
g, customerUser1041, customer001, /orgs/1/sites/site001
g, customerUser1041, customer001, /orgs/1/sites/site002
g, customerUser1041, customer001, /orgs/1/sites/site003
g, customerUser1041, customer001, /orgs/1/sites/site004
g, customerUser1041, customer001, /orgs/1/sites/site005
g, customerUser1042, customer001, /orgs/1/sites/site001
g, customerUser1042, customer001, /orgs/1/sites/site002
g, customerUser1042, customer001, /orgs/1/sites/site003
g, customerUser1042, customer001, /orgs/1/sites/site004
g, customerUser1042, customer001, /orgs/1/sites/site005
g, customerUser1043, customer001, /orgs/1/sites/site001
g, customerUser1043, customer001, /orgs/1/sites/site002
g, customerUser1043, customer001, /orgs/1/sites/site003
g, customerUser1043, customer001, /orgs/1/sites/site004
g, customerUser1043, customer001, /orgs/1/sites/site005
g, customerUser1044, customer001, /orgs/1/sites/site001
g, customerUser1044, customer001, /orgs/1/sites/site002
g, customerUser1044, customer001, /orgs/1/sites/site003
g, customerUser1044, customer001, /orgs/1/sites/site004
g, customerUser1044, customer001, /orgs/1/sites/site005
g, customerUser1045, customer001, /orgs/1/sites/site001
g, customerUser1045, customer001, /orgs/1/sites/site002
g, customerUser1045, customer001, /orgs/1/sites/site003
g, customerUser1045, customer001, /orgs/1/sites/site004
g, customerUser1045, customer001, /orgs/1/sites/site005
g, customerUser1046, customer001, /orgs/1/sites/site001
g, customerUser1046, customer001, /orgs/1/sites/site002
g, customerUser1046, customer001, /orgs/1/sites/site003
g, customerUser1046, customer001, /orgs/1/sites/site004
g, customerUser1046, customer001, /orgs/1/sites/site005
g, customerUser1047, customer001, /orgs/1/sites/site001
g, customerUser1047, customer001, /orgs/1/sites/site002
g, customerUser1047, customer001, /orgs/1/sites/site003
g, customerUser1047, customer001, /orgs/1/sites/site004
g, customerUser1047, customer001, /orgs/1/sites/site005
g, customerUser1048, customer001, /orgs/1/sites/site001
g, customerUser1048, customer001, /orgs/1/sites/site002
g, customerUser1048, customer001, /orgs/1/sites/site003
g, customerUser1048, customer001, /orgs/1/sites/site004
g, customerUser1048, customer001, /orgs/1/sites/site005
g, customerUser1049, customer001, /orgs/1/sites/site001
g, customerUser1049, customer001, /orgs/1/sites/site002
g, customerUser1049, customer001, /orgs/1/sites/site003
g, customerUser1049, customer001, /orgs/1/sites/site004
g, customerUser1049, customer001, /orgs/1/sites/site005
g, customerUser1050, customer001, /orgs/1/sites/site001
g, customerUser1050, customer001, /orgs/1/sites/site002
g, customerUser1050, customer001, /orgs/1/sites/site003
g, customerUser1050, customer001, /orgs/1/sites/site004
g, customerUser1050, customer001, /orgs/1/sites/site005
# Group - customer001, / org1
g, customerUser2001, customer001, /orgs/1/sites/site001
g, customerUser2001, customer001, /orgs/1/sites/site002
g, customerUser2001, customer001, /orgs/1/sites/site003
g, customerUser2001, customer001, /orgs/1/sites/site004
g, customerUser2001, customer001, /orgs/1/sites/site005
g, customerUser2001, customer001, /orgs/1/sites/site001
g, customerUser2001, customer001, /orgs/1/sites/site002
g, customerUser2001, customer001, /orgs/1/sites/site003
g, customerUser2001, customer001, /orgs/1/sites/site004
g, customerUser2001, customer001, /orgs/1/sites/site005
g, customerUser2003, customer001, /orgs/1/sites/site001
g, customerUser2003, customer001, /orgs/1/sites/site002
g, customerUser2003, customer001, /orgs/1/sites/site003
g, customerUser2003, customer001, /orgs/1/sites/site004
g, customerUser2003, customer001, /orgs/1/sites/site005
g, customerUser2004, customer001, /orgs/1/sites/site001
g, customerUser2004, customer001, /orgs/1/sites/site002
g, customerUser2004, customer001, /orgs/1/sites/site003
g, customerUser2004, customer001, /orgs/1/sites/site004
g, customerUser2004, customer001, /orgs/1/sites/site005
g, customerUser2005, customer001, /orgs/1/sites/site001
g, customerUser2005, customer001, /orgs/1/sites/site002
g, customerUser2005, customer001, /orgs/1/sites/site003
g, customerUser2005, customer001, /orgs/1/sites/site004
g, customerUser2005, customer001, /orgs/1/sites/site005
g, customerUser2006, customer001, /orgs/1/sites/site001
g, customerUser2006, customer001, /orgs/1/sites/site002
g, customerUser2006, customer001, /orgs/1/sites/site003
g, customerUser2006, customer001, /orgs/1/sites/site004
g, customerUser2006, customer001, /orgs/1/sites/site005
g, customerUser2007, customer001, /orgs/1/sites/site001
g, customerUser2007, customer001, /orgs/1/sites/site002
g, customerUser2007, customer001, /orgs/1/sites/site003
g, customerUser2007, customer001, /orgs/1/sites/site004
g, customerUser2007, customer001, /orgs/1/sites/site005
g, customerUser2008, customer001, /orgs/1/sites/site001
g, customerUser2008, customer001, /orgs/1/sites/site002
g, customerUser2008, customer001, /orgs/1/sites/site003
g, customerUser2008, customer001, /orgs/1/sites/site004
g, customerUser2008, customer001, /orgs/1/sites/site005
g, customerUser2009, customer001, /orgs/1/sites/site001
g, customerUser2009, customer001, /orgs/1/sites/site002
g, customerUser2009, customer001, /orgs/1/sites/site003
g, customerUser2009, customer001, /orgs/1/sites/site004
g, customerUser2009, customer001, /orgs/1/sites/site005
g, customerUser2010, customer001, /orgs/1/sites/site001
g, customerUser2010, customer001, /orgs/1/sites/site002
g, customerUser2010, customer001, /orgs/1/sites/site003
g, customerUser2010, customer001, /orgs/1/sites/site004
g, customerUser2010, customer001, /orgs/1/sites/site005
g, customerUser2011, customer001, /orgs/1/sites/site001
g, customerUser2011, customer001, /orgs/1/sites/site002
g, customerUser2011, customer001, /orgs/1/sites/site003
g, customerUser2011, customer001, /orgs/1/sites/site004
g, customerUser2011, customer001, /orgs/1/sites/site005
g, customerUser2012, customer001, /orgs/1/sites/site001
g, customerUser2012, customer001, /orgs/1/sites/site002
g, customerUser2012, customer001, /orgs/1/sites/site003
g, customerUser2012, customer001, /orgs/1/sites/site004
g, customerUser2012, customer001, /orgs/1/sites/site005
g, customerUser2013, customer001, /orgs/1/sites/site001
g, customerUser2013, customer001, /orgs/1/sites/site002
g, customerUser2013, customer001, /orgs/1/sites/site003
g, customerUser2013, customer001, /orgs/1/sites/site004
g, customerUser2013, customer001, /orgs/1/sites/site005
g, customerUser2014, customer001, /orgs/1/sites/site001
g, customerUser2014, customer001, /orgs/1/sites/site002
g, customerUser2014, customer001, /orgs/1/sites/site003
g, customerUser2014, customer001, /orgs/1/sites/site004
g, customerUser2014, customer001, /orgs/1/sites/site005
g, customerUser2015, customer001, /orgs/1/sites/site001
g, customerUser2015, customer001, /orgs/1/sites/site002
g, customerUser2015, customer001, /orgs/1/sites/site003
g, customerUser2015, customer001, /orgs/1/sites/site004
g, customerUser2015, customer001, /orgs/1/sites/site005
g, customerUser2016, customer001, /orgs/1/sites/site001
g, customerUser2016, customer001, /orgs/1/sites/site002
g, customerUser2016, customer001, /orgs/1/sites/site003
g, customerUser2016, customer001, /orgs/1/sites/site004
g, customerUser2016, customer001, /orgs/1/sites/site005
g, customerUser2017, customer001, /orgs/1/sites/site001
g, customerUser2017, customer001, /orgs/1/sites/site002
g, customerUser2017, customer001, /orgs/1/sites/site003
g, customerUser2017, customer001, /orgs/1/sites/site004
g, customerUser2017, customer001, /orgs/1/sites/site005
g, customerUser2018, customer001, /orgs/1/sites/site001
g, customerUser2018, customer001, /orgs/1/sites/site002
g, customerUser2018, customer001, /orgs/1/sites/site003
g, customerUser2018, customer001, /orgs/1/sites/site004
g, customerUser2018, customer001, /orgs/1/sites/site005
g, customerUser2019, customer001, /orgs/1/sites/site001
g, customerUser2019, customer001, /orgs/1/sites/site002
g, customerUser2019, customer001, /orgs/1/sites/site003
g, customerUser2019, customer001, /orgs/1/sites/site004
g, customerUser2019, customer001, /orgs/1/sites/site005
g, customerUser2020, customer001, /orgs/1/sites/site001
g, customerUser2020, customer001, /orgs/1/sites/site002
g, customerUser2020, customer001, /orgs/1/sites/site003
g, customerUser2020, customer001, /orgs/1/sites/site004
g, customerUser2020, customer001, /orgs/1/sites/site005
g, customerUser2021, customer001, /orgs/1/sites/site001
g, customerUser2021, customer001, /orgs/1/sites/site002
g, customerUser2021, customer001, /orgs/1/sites/site003
g, customerUser2021, customer001, /orgs/1/sites/site004
g, customerUser2021, customer001, /orgs/1/sites/site005
g, customerUser2022, customer001, /orgs/1/sites/site001
g, customerUser2022, customer001, /orgs/1/sites/site002
g, customerUser2022, customer001, /orgs/1/sites/site003
g, customerUser2022, customer001, /orgs/1/sites/site004
g, customerUser2022, customer001, /orgs/1/sites/site005
g, customerUser2023, customer001, /orgs/1/sites/site001
g, customerUser2023, customer001, /orgs/1/sites/site002
g, customerUser2023, customer001, /orgs/1/sites/site003
g, customerUser2023, customer001, /orgs/1/sites/site004
g, customerUser2023, customer001, /orgs/1/sites/site005
g, customerUser2024, customer001, /orgs/1/sites/site001
g, customerUser2024, customer001, /orgs/1/sites/site002
g, customerUser2024, customer001, /orgs/1/sites/site003
g, customerUser2024, customer001, /orgs/1/sites/site004
g, customerUser2024, customer001, /orgs/1/sites/site005
g, customerUser2025, customer001, /orgs/1/sites/site001
g, customerUser2025, customer001, /orgs/1/sites/site002
g, customerUser2025, customer001, /orgs/1/sites/site003
g, customerUser2025, customer001, /orgs/1/sites/site004
g, customerUser2025, customer001, /orgs/1/sites/site005
g, customerUser2026, customer001, /orgs/1/sites/site001
g, customerUser2026, customer001, /orgs/1/sites/site002
g, customerUser2026, customer001, /orgs/1/sites/site003
g, customerUser2026, customer001, /orgs/1/sites/site004
g, customerUser2026, customer001, /orgs/1/sites/site005
g, customerUser2027, customer001, /orgs/1/sites/site001
g, customerUser2027, customer001, /orgs/1/sites/site002
g, customerUser2027, customer001, /orgs/1/sites/site003
g, customerUser2027, customer001, /orgs/1/sites/site004
g, customerUser2027, customer001, /orgs/1/sites/site005
g, customerUser2028, customer001, /orgs/1/sites/site001
g, customerUser2028, customer001, /orgs/1/sites/site002
g, customerUser2028, customer001, /orgs/1/sites/site003
g, customerUser2028, customer001, /orgs/1/sites/site004
g, customerUser2028, customer001, /orgs/1/sites/site005
g, customerUser2029, customer001, /orgs/1/sites/site001
g, customerUser2029, customer001, /orgs/1/sites/site002
g, customerUser2029, customer001, /orgs/1/sites/site003
g, customerUser2029, customer001, /orgs/1/sites/site004
g, customerUser2029, customer001, /orgs/1/sites/site005
g, customerUser2030, customer001, /orgs/1/sites/site001
g, customerUser2030, customer001, /orgs/1/sites/site002
g, customerUser2030, customer001, /orgs/1/sites/site003
g, customerUser2030, customer001, /orgs/1/sites/site004
g, customerUser2030, customer001, /orgs/1/sites/site005
g, customerUser2031, customer001, /orgs/1/sites/site001
g, customerUser2031, customer001, /orgs/1/sites/site002
g, customerUser2031, customer001, /orgs/1/sites/site003
g, customerUser2031, customer001, /orgs/1/sites/site004
g, customerUser2031, customer001, /orgs/1/sites/site005
g, customerUser2032, customer001, /orgs/1/sites/site001
g, customerUser2032, customer001, /orgs/1/sites/site002
g, customerUser2032, customer001, /orgs/1/sites/site003
g, customerUser2032, customer001, /orgs/1/sites/site004
g, customerUser2032, customer001, /orgs/1/sites/site005
g, customerUser2033, customer001, /orgs/1/sites/site001
g, customerUser2033, customer001, /orgs/1/sites/site002
g, customerUser2033, customer001, /orgs/1/sites/site003
g, customerUser2033, customer001, /orgs/1/sites/site004
g, customerUser2033, customer001, /orgs/1/sites/site005
g, customerUser2034, customer001, /orgs/1/sites/site001
g, customerUser2034, customer001, /orgs/1/sites/site002
g, customerUser2034, customer001, /orgs/1/sites/site003
g, customerUser2034, customer001, /orgs/1/sites/site004
g, customerUser2034, customer001, /orgs/1/sites/site005
g, customerUser2035, customer001, /orgs/1/sites/site001
g, customerUser2035, customer001, /orgs/1/sites/site002
g, customerUser2035, customer001, /orgs/1/sites/site003
g, customerUser2035, customer001, /orgs/1/sites/site004
g, customerUser2035, customer001, /orgs/1/sites/site005
g, customerUser2036, customer001, /orgs/1/sites/site001
g, customerUser2036, customer001, /orgs/1/sites/site002
g, customerUser2036, customer001, /orgs/1/sites/site003
g, customerUser2036, customer001, /orgs/1/sites/site004
g, customerUser2036, customer001, /orgs/1/sites/site005
g, customerUser2037, customer001, /orgs/1/sites/site001
g, customerUser2037, customer001, /orgs/1/sites/site002
g, customerUser2037, customer001, /orgs/1/sites/site003
g, customerUser2037, customer001, /orgs/1/sites/site004
g, customerUser2037, customer001, /orgs/1/sites/site005
g, customerUser2038, customer001, /orgs/1/sites/site001
g, customerUser2038, customer001, /orgs/1/sites/site002
g, customerUser2038, customer001, /orgs/1/sites/site003
g, customerUser2038, customer001, /orgs/1/sites/site004
g, customerUser2038, customer001, /orgs/1/sites/site005
g, customerUser2039, customer001, /orgs/1/sites/site001
g, customerUser2039, customer001, /orgs/1/sites/site002
g, customerUser2039, customer001, /orgs/1/sites/site003
g, customerUser2039, customer001, /orgs/1/sites/site004
g, customerUser2039, customer001, /orgs/1/sites/site005
g, customerUser2040, customer001, /orgs/1/sites/site001
g, customerUser2040, customer001, /orgs/1/sites/site002
g, customerUser2040, customer001, /orgs/1/sites/site003
g, customerUser2040, customer001, /orgs/1/sites/site004
g, customerUser2040, customer001, /orgs/1/sites/site005
g, customerUser2041, customer001, /orgs/1/sites/site001
g, customerUser2041, customer001, /orgs/1/sites/site002
g, customerUser2041, customer001, /orgs/1/sites/site003
g, customerUser2041, customer001, /orgs/1/sites/site004
g, customerUser2041, customer001, /orgs/1/sites/site005
g, customerUser2042, customer001, /orgs/1/sites/site001
g, customerUser2042, customer001, /orgs/1/sites/site002
g, customerUser2042, customer001, /orgs/1/sites/site003
g, customerUser2042, customer001, /orgs/1/sites/site004
g, customerUser2042, customer001, /orgs/1/sites/site005
g, customerUser2043, customer001, /orgs/1/sites/site001
g, customerUser2043, customer001, /orgs/1/sites/site002
g, customerUser2043, customer001, /orgs/1/sites/site003
g, customerUser2043, customer001, /orgs/1/sites/site004
g, customerUser2043, customer001, /orgs/1/sites/site005
g, customerUser2044, customer001, /orgs/1/sites/site001
g, customerUser2044, customer001, /orgs/1/sites/site002
g, customerUser2044, customer001, /orgs/1/sites/site003
g, customerUser2044, customer001, /orgs/1/sites/site004
g, customerUser2044, customer001, /orgs/1/sites/site005
g, customerUser2045, customer001, /orgs/1/sites/site001
g, customerUser2045, customer001, /orgs/1/sites/site002
g, customerUser2045, customer001, /orgs/1/sites/site003
g, customerUser2045, customer001, /orgs/1/sites/site004
g, customerUser2045, customer001, /orgs/1/sites/site005
g, customerUser2046, customer001, /orgs/1/sites/site001
g, customerUser2046, customer001, /orgs/1/sites/site002
g, customerUser2046, customer001, /orgs/1/sites/site003
g, customerUser2046, customer001, /orgs/1/sites/site004
g, customerUser2046, customer001, /orgs/1/sites/site005
g, customerUser2047, customer001, /orgs/1/sites/site001
g, customerUser2047, customer001, /orgs/1/sites/site002
g, customerUser2047, customer001, /orgs/1/sites/site003
g, customerUser2047, customer001, /orgs/1/sites/site004
g, customerUser2047, customer001, /orgs/1/sites/site005
g, customerUser2048, customer001, /orgs/1/sites/site001
g, customerUser2048, customer001, /orgs/1/sites/site002
g, customerUser2048, customer001, /orgs/1/sites/site003
g, customerUser2048, customer001, /orgs/1/sites/site004
g, customerUser2048, customer001, /orgs/1/sites/site005
g, customerUser2049, customer001, /orgs/1/sites/site001
g, customerUser2049, customer001, /orgs/1/sites/site002
g, customerUser2049, customer001, /orgs/1/sites/site003
g, customerUser2049, customer001, /orgs/1/sites/site004
g, customerUser2049, customer001, /orgs/1/sites/site005
g, customerUser2050, customer001, /orgs/1/sites/site001
g, customerUser2050, customer001, /orgs/1/sites/site002
g, customerUser2050, customer001, /orgs/1/sites/site003
g, customerUser2050, customer001, /orgs/1/sites/site004
g, customerUser2050, customer001, /orgs/1/sites/site005
# Group - staff001, / org2
g, staffUser1001, staff001, /orgs/2/sites/site001
g, staffUser1001, staff001, /orgs/2/sites/site002
g, staffUser1001, staff001, /orgs/2/sites/site003
g, staffUser1001, staff001, /orgs/2/sites/site004
g, staffUser1001, staff001, /orgs/2/sites/site005
g, staffUser1001, staff001, /orgs/2/sites/site001
g, staffUser1001, staff001, /orgs/2/sites/site002
g, staffUser1001, staff001, /orgs/2/sites/site003
g, staffUser1001, staff001, /orgs/2/sites/site004
g, staffUser1001, staff001, /orgs/2/sites/site005
g, staffUser1003, staff001, /orgs/2/sites/site001
g, staffUser1003, staff001, /orgs/2/sites/site002
g, staffUser1003, staff001, /orgs/2/sites/site003
g, staffUser1003, staff001, /orgs/2/sites/site004
g, staffUser1003, staff001, /orgs/2/sites/site005
g, staffUser1004, staff001, /orgs/2/sites/site001
g, staffUser1004, staff001, /orgs/2/sites/site002
g, staffUser1004, staff001, /orgs/2/sites/site003
g, staffUser1004, staff001, /orgs/2/sites/site004
g, staffUser1004, staff001, /orgs/2/sites/site005
g, staffUser1005, staff001, /orgs/2/sites/site001
g, staffUser1005, staff001, /orgs/2/sites/site002
g, staffUser1005, staff001, /orgs/2/sites/site003
g, staffUser1005, staff001, /orgs/2/sites/site004
g, staffUser1005, staff001, /orgs/2/sites/site005
g, staffUser1006, staff001, /orgs/2/sites/site001
g, staffUser1006, staff001, /orgs/2/sites/site002
g, staffUser1006, staff001, /orgs/2/sites/site003
g, staffUser1006, staff001, /orgs/2/sites/site004
g, staffUser1006, staff001, /orgs/2/sites/site005
g, staffUser1007, staff001, /orgs/2/sites/site001
g, staffUser1007, staff001, /orgs/2/sites/site002
g, staffUser1007, staff001, /orgs/2/sites/site003
g, staffUser1007, staff001, /orgs/2/sites/site004
g, staffUser1007, staff001, /orgs/2/sites/site005
g, staffUser1008, staff001, /orgs/2/sites/site001
g, staffUser1008, staff001, /orgs/2/sites/site002
g, staffUser1008, staff001, /orgs/2/sites/site003
g, staffUser1008, staff001, /orgs/2/sites/site004
g, staffUser1008, staff001, /orgs/2/sites/site005
g, staffUser1009, staff001, /orgs/2/sites/site001
g, staffUser1009, staff001, /orgs/2/sites/site002
g, staffUser1009, staff001, /orgs/2/sites/site003
g, staffUser1009, staff001, /orgs/2/sites/site004
g, staffUser1009, staff001, /orgs/2/sites/site005
g, staffUser1010, staff001, /orgs/2/sites/site001
g, staffUser1010, staff001, /orgs/2/sites/site002
g, staffUser1010, staff001, /orgs/2/sites/site003
g, staffUser1010, staff001, /orgs/2/sites/site004
g, staffUser1010, staff001, /orgs/2/sites/site005
g, staffUser1011, staff001, /orgs/2/sites/site001
g, staffUser1011, staff001, /orgs/2/sites/site002
g, staffUser1011, staff001, /orgs/2/sites/site003
g, staffUser1011, staff001, /orgs/2/sites/site004
g, staffUser1011, staff001, /orgs/2/sites/site005
g, staffUser1012, staff001, /orgs/2/sites/site001
g, staffUser1012, staff001, /orgs/2/sites/site002
g, staffUser1012, staff001, /orgs/2/sites/site003
g, staffUser1012, staff001, /orgs/2/sites/site004
g, staffUser1012, staff001, /orgs/2/sites/site005
g, staffUser1013, staff001, /orgs/2/sites/site001
g, staffUser1013, staff001, /orgs/2/sites/site002
g, staffUser1013, staff001, /orgs/2/sites/site003
g, staffUser1013, staff001, /orgs/2/sites/site004
g, staffUser1013, staff001, /orgs/2/sites/site005
g, staffUser1014, staff001, /orgs/2/sites/site001
g, staffUser1014, staff001, /orgs/2/sites/site002
g, staffUser1014, staff001, /orgs/2/sites/site003
g, staffUser1014, staff001, /orgs/2/sites/site004
g, staffUser1014, staff001, /orgs/2/sites/site005
g, staffUser1015, staff001, /orgs/2/sites/site001
g, staffUser1015, staff001, /orgs/2/sites/site002
g, staffUser1015, staff001, /orgs/2/sites/site003
g, staffUser1015, staff001, /orgs/2/sites/site004
g, staffUser1015, staff001, /orgs/2/sites/site005
g, staffUser1016, staff001, /orgs/2/sites/site001
g, staffUser1016, staff001, /orgs/2/sites/site002
g, staffUser1016, staff001, /orgs/2/sites/site003
g, staffUser1016, staff001, /orgs/2/sites/site004
g, staffUser1016, staff001, /orgs/2/sites/site005
g, staffUser1017, staff001, /orgs/2/sites/site001
g, staffUser1017, staff001, /orgs/2/sites/site002
g, staffUser1017, staff001, /orgs/2/sites/site003
g, staffUser1017, staff001, /orgs/2/sites/site004
g, staffUser1017, staff001, /orgs/2/sites/site005
g, staffUser1018, staff001, /orgs/2/sites/site001
g, staffUser1018, staff001, /orgs/2/sites/site002
g, staffUser1018, staff001, /orgs/2/sites/site003
g, staffUser1018, staff001, /orgs/2/sites/site004
g, staffUser1018, staff001, /orgs/2/sites/site005
g, staffUser1019, staff001, /orgs/2/sites/site001
g, staffUser1019, staff001, /orgs/2/sites/site002
g, staffUser1019, staff001, /orgs/2/sites/site003
g, staffUser1019, staff001, /orgs/2/sites/site004
g, staffUser1019, staff001, /orgs/2/sites/site005
g, staffUser1020, staff001, /orgs/2/sites/site001
g, staffUser1020, staff001, /orgs/2/sites/site002
g, staffUser1020, staff001, /orgs/2/sites/site003
g, staffUser1020, staff001, /orgs/2/sites/site004
g, staffUser1020, staff001, /orgs/2/sites/site005
g, staffUser1021, staff001, /orgs/2/sites/site001
g, staffUser1021, staff001, /orgs/2/sites/site002
g, staffUser1021, staff001, /orgs/2/sites/site003
g, staffUser1021, staff001, /orgs/2/sites/site004
g, staffUser1021, staff001, /orgs/2/sites/site005
g, staffUser1022, staff001, /orgs/2/sites/site001
g, staffUser1022, staff001, /orgs/2/sites/site002
g, staffUser1022, staff001, /orgs/2/sites/site003
g, staffUser1022, staff001, /orgs/2/sites/site004
g, staffUser1022, staff001, /orgs/2/sites/site005
g, staffUser1023, staff001, /orgs/2/sites/site001
g, staffUser1023, staff001, /orgs/2/sites/site002
g, staffUser1023, staff001, /orgs/2/sites/site003
g, staffUser1023, staff001, /orgs/2/sites/site004
g, staffUser1023, staff001, /orgs/2/sites/site005
g, staffUser1024, staff001, /orgs/2/sites/site001
g, staffUser1024, staff001, /orgs/2/sites/site002
g, staffUser1024, staff001, /orgs/2/sites/site003
g, staffUser1024, staff001, /orgs/2/sites/site004
g, staffUser1024, staff001, /orgs/2/sites/site005
g, staffUser1025, staff001, /orgs/2/sites/site001
g, staffUser1025, staff001, /orgs/2/sites/site002
g, staffUser1025, staff001, /orgs/2/sites/site003
g, staffUser1025, staff001, /orgs/2/sites/site004
g, staffUser1025, staff001, /orgs/2/sites/site005
g, staffUser1026, staff001, /orgs/2/sites/site001
g, staffUser1026, staff001, /orgs/2/sites/site002
g, staffUser1026, staff001, /orgs/2/sites/site003
g, staffUser1026, staff001, /orgs/2/sites/site004
g, staffUser1026, staff001, /orgs/2/sites/site005
g, staffUser1027, staff001, /orgs/2/sites/site001
g, staffUser1027, staff001, /orgs/2/sites/site002
g, staffUser1027, staff001, /orgs/2/sites/site003
g, staffUser1027, staff001, /orgs/2/sites/site004
g, staffUser1027, staff001, /orgs/2/sites/site005
g, staffUser1028, staff001, /orgs/2/sites/site001
g, staffUser1028, staff001, /orgs/2/sites/site002
g, staffUser1028, staff001, /orgs/2/sites/site003
g, staffUser1028, staff001, /orgs/2/sites/site004
g, staffUser1028, staff001, /orgs/2/sites/site005
g, staffUser1029, staff001, /orgs/2/sites/site001
g, staffUser1029, staff001, /orgs/2/sites/site002
g, staffUser1029, staff001, /orgs/2/sites/site003
g, staffUser1029, staff001, /orgs/2/sites/site004
g, staffUser1029, staff001, /orgs/2/sites/site005
g, staffUser1030, staff001, /orgs/2/sites/site001
g, staffUser1030, staff001, /orgs/2/sites/site002
g, staffUser1030, staff001, /orgs/2/sites/site003
g, staffUser1030, staff001, /orgs/2/sites/site004
g, staffUser1030, staff001, /orgs/2/sites/site005
g, staffUser1031, staff001, /orgs/2/sites/site001
g, staffUser1031, staff001, /orgs/2/sites/site002
g, staffUser1031, staff001, /orgs/2/sites/site003
g, staffUser1031, staff001, /orgs/2/sites/site004
g, staffUser1031, staff001, /orgs/2/sites/site005
g, staffUser1032, staff001, /orgs/2/sites/site001
g, staffUser1032, staff001, /orgs/2/sites/site002
g, staffUser1032, staff001, /orgs/2/sites/site003
g, staffUser1032, staff001, /orgs/2/sites/site004
g, staffUser1032, staff001, /orgs/2/sites/site005
g, staffUser1033, staff001, /orgs/2/sites/site001
g, staffUser1033, staff001, /orgs/2/sites/site002
g, staffUser1033, staff001, /orgs/2/sites/site003
g, staffUser1033, staff001, /orgs/2/sites/site004
g, staffUser1033, staff001, /orgs/2/sites/site005
g, staffUser1034, staff001, /orgs/2/sites/site001
g, staffUser1034, staff001, /orgs/2/sites/site002
g, staffUser1034, staff001, /orgs/2/sites/site003
g, staffUser1034, staff001, /orgs/2/sites/site004
g, staffUser1034, staff001, /orgs/2/sites/site005
g, staffUser1035, staff001, /orgs/2/sites/site001
g, staffUser1035, staff001, /orgs/2/sites/site002
g, staffUser1035, staff001, /orgs/2/sites/site003
g, staffUser1035, staff001, /orgs/2/sites/site004
g, staffUser1035, staff001, /orgs/2/sites/site005
g, staffUser1036, staff001, /orgs/2/sites/site001
g, staffUser1036, staff001, /orgs/2/sites/site002
g, staffUser1036, staff001, /orgs/2/sites/site003
g, staffUser1036, staff001, /orgs/2/sites/site004
g, staffUser1036, staff001, /orgs/2/sites/site005
g, staffUser1037, staff001, /orgs/2/sites/site001
g, staffUser1037, staff001, /orgs/2/sites/site002
g, staffUser1037, staff001, /orgs/2/sites/site003
g, staffUser1037, staff001, /orgs/2/sites/site004
g, staffUser1037, staff001, /orgs/2/sites/site005
g, staffUser1038, staff001, /orgs/2/sites/site001
g, staffUser1038, staff001, /orgs/2/sites/site002
g, staffUser1038, staff001, /orgs/2/sites/site003
g, staffUser1038, staff001, /orgs/2/sites/site004
g, staffUser1038, staff001, /orgs/2/sites/site005
g, staffUser1039, staff001, /orgs/2/sites/site001
g, staffUser1039, staff001, /orgs/2/sites/site002
g, staffUser1039, staff001, /orgs/2/sites/site003
g, staffUser1039, staff001, /orgs/2/sites/site004
g, staffUser1039, staff001, /orgs/2/sites/site005
g, staffUser1040, staff001, /orgs/2/sites/site001
g, staffUser1040, staff001, /orgs/2/sites/site002
g, staffUser1040, staff001, /orgs/2/sites/site003
g, staffUser1040, staff001, /orgs/2/sites/site004
g, staffUser1040, staff001, /orgs/2/sites/site005
g, staffUser1041, staff001, /orgs/2/sites/site001
g, staffUser1041, staff001, /orgs/2/sites/site002
g, staffUser1041, staff001, /orgs/2/sites/site003
g, staffUser1041, staff001, /orgs/2/sites/site004
g, staffUser1041, staff001, /orgs/2/sites/site005
g, staffUser1042, staff001, /orgs/2/sites/site001
g, staffUser1042, staff001, /orgs/2/sites/site002
g, staffUser1042, staff001, /orgs/2/sites/site003
g, staffUser1042, staff001, /orgs/2/sites/site004
g, staffUser1042, staff001, /orgs/2/sites/site005
g, staffUser1043, staff001, /orgs/2/sites/site001
g, staffUser1043, staff001, /orgs/2/sites/site002
g, staffUser1043, staff001, /orgs/2/sites/site003
g, staffUser1043, staff001, /orgs/2/sites/site004
g, staffUser1043, staff001, /orgs/2/sites/site005
g, staffUser1044, staff001, /orgs/2/sites/site001
g, staffUser1044, staff001, /orgs/2/sites/site002
g, staffUser1044, staff001, /orgs/2/sites/site003
g, staffUser1044, staff001, /orgs/2/sites/site004
g, staffUser1044, staff001, /orgs/2/sites/site005
g, staffUser1045, staff001, /orgs/2/sites/site001
g, staffUser1045, staff001, /orgs/2/sites/site002
g, staffUser1045, staff001, /orgs/2/sites/site003
g, staffUser1045, staff001, /orgs/2/sites/site004
g, staffUser1045, staff001, /orgs/2/sites/site005
g, staffUser1046, staff001, /orgs/2/sites/site001
g, staffUser1046, staff001, /orgs/2/sites/site002
g, staffUser1046, staff001, /orgs/2/sites/site003
g, staffUser1046, staff001, /orgs/2/sites/site004
g, staffUser1046, staff001, /orgs/2/sites/site005
g, staffUser1047, staff001, /orgs/2/sites/site001
g, staffUser1047, staff001, /orgs/2/sites/site002
g, staffUser1047, staff001, /orgs/2/sites/site003
g, staffUser1047, staff001, /orgs/2/sites/site004
g, staffUser1047, staff001, /orgs/2/sites/site005
g, staffUser1048, staff001, /orgs/2/sites/site001
g, staffUser1048, staff001, /orgs/2/sites/site002
g, staffUser1048, staff001, /orgs/2/sites/site003
g, staffUser1048, staff001, /orgs/2/sites/site004
g, staffUser1048, staff001, /orgs/2/sites/site005
g, staffUser1049, staff001, /orgs/2/sites/site001
g, staffUser1049, staff001, /orgs/2/sites/site002
g, staffUser1049, staff001, /orgs/2/sites/site003
g, staffUser1049, staff001, /orgs/2/sites/site004
g, staffUser1049, staff001, /orgs/2/sites/site005
g, staffUser1050, staff001, /orgs/2/sites/site001
g, staffUser1050, staff001, /orgs/2/sites/site002
g, staffUser1050, staff001, /orgs/2/sites/site003
g, staffUser1050, staff001, /orgs/2/sites/site004
g, staffUser1050, staff001, /orgs/2/sites/site005
# Group - staff001, / org2
g, staffUser2001, staff001, /orgs/2/sites/site001
g, staffUser2001, staff001, /orgs/2/sites/site002
g, staffUser2001, staff001, /orgs/2/sites/site003
g, staffUser2001, staff001, /orgs/2/sites/site004
g, staffUser2001, staff001, /orgs/2/sites/site005
g, staffUser2001, staff001, /orgs/2/sites/site001
g, staffUser2001, staff001, /orgs/2/sites/site002
g, staffUser2001, staff001, /orgs/2/sites/site003
g, staffUser2001, staff001, /orgs/2/sites/site004
g, staffUser2001, staff001, /orgs/2/sites/site005
g, staffUser2003, staff001, /orgs/2/sites/site001
g, staffUser2003, staff001, /orgs/2/sites/site002
g, staffUser2003, staff001, /orgs/2/sites/site003
g, staffUser2003, staff001, /orgs/2/sites/site004
g, staffUser2003, staff001, /orgs/2/sites/site005
g, staffUser2004, staff001, /orgs/2/sites/site001
g, staffUser2004, staff001, /orgs/2/sites/site002
g, staffUser2004, staff001, /orgs/2/sites/site003
g, staffUser2004, staff001, /orgs/2/sites/site004
g, staffUser2004, staff001, /orgs/2/sites/site005
g, staffUser2005, staff001, /orgs/2/sites/site001
g, staffUser2005, staff001, /orgs/2/sites/site002
g, staffUser2005, staff001, /orgs/2/sites/site003
g, staffUser2005, staff001, /orgs/2/sites/site004
g, staffUser2005, staff001, /orgs/2/sites/site005
g, staffUser2006, staff001, /orgs/2/sites/site001
g, staffUser2006, staff001, /orgs/2/sites/site002
g, staffUser2006, staff001, /orgs/2/sites/site003
g, staffUser2006, staff001, /orgs/2/sites/site004
g, staffUser2006, staff001, /orgs/2/sites/site005
g, staffUser2007, staff001, /orgs/2/sites/site001
g, staffUser2007, staff001, /orgs/2/sites/site002
g, staffUser2007, staff001, /orgs/2/sites/site003
g, staffUser2007, staff001, /orgs/2/sites/site004
g, staffUser2007, staff001, /orgs/2/sites/site005
g, staffUser2008, staff001, /orgs/2/sites/site001
g, staffUser2008, staff001, /orgs/2/sites/site002
g, staffUser2008, staff001, /orgs/2/sites/site003
g, staffUser2008, staff001, /orgs/2/sites/site004
g, staffUser2008, staff001, /orgs/2/sites/site005
g, staffUser2009, staff001, /orgs/2/sites/site001
g, staffUser2009, staff001, /orgs/2/sites/site002
g, staffUser2009, staff001, /orgs/2/sites/site003
g, staffUser2009, staff001, /orgs/2/sites/site004
g, staffUser2009, staff001, /orgs/2/sites/site005
g, staffUser2010, staff001, /orgs/2/sites/site001
g, staffUser2010, staff001, /orgs/2/sites/site002
g, staffUser2010, staff001, /orgs/2/sites/site003
g, staffUser2010, staff001, /orgs/2/sites/site004
g, staffUser2010, staff001, /orgs/2/sites/site005
g, staffUser2011, staff001, /orgs/2/sites/site001
g, staffUser2011, staff001, /orgs/2/sites/site002
g, staffUser2011, staff001, /orgs/2/sites/site003
g, staffUser2011, staff001, /orgs/2/sites/site004
g, staffUser2011, staff001, /orgs/2/sites/site005
g, staffUser2012, staff001, /orgs/2/sites/site001
g, staffUser2012, staff001, /orgs/2/sites/site002
g, staffUser2012, staff001, /orgs/2/sites/site003
g, staffUser2012, staff001, /orgs/2/sites/site004
g, staffUser2012, staff001, /orgs/2/sites/site005
g, staffUser2013, staff001, /orgs/2/sites/site001
g, staffUser2013, staff001, /orgs/2/sites/site002
g, staffUser2013, staff001, /orgs/2/sites/site003
g, staffUser2013, staff001, /orgs/2/sites/site004
g, staffUser2013, staff001, /orgs/2/sites/site005
g, staffUser2014, staff001, /orgs/2/sites/site001
g, staffUser2014, staff001, /orgs/2/sites/site002
g, staffUser2014, staff001, /orgs/2/sites/site003
g, staffUser2014, staff001, /orgs/2/sites/site004
g, staffUser2014, staff001, /orgs/2/sites/site005
g, staffUser2015, staff001, /orgs/2/sites/site001
g, staffUser2015, staff001, /orgs/2/sites/site002
g, staffUser2015, staff001, /orgs/2/sites/site003
g, staffUser2015, staff001, /orgs/2/sites/site004
g, staffUser2015, staff001, /orgs/2/sites/site005
g, staffUser2016, staff001, /orgs/2/sites/site001
g, staffUser2016, staff001, /orgs/2/sites/site002
g, staffUser2016, staff001, /orgs/2/sites/site003
g, staffUser2016, staff001, /orgs/2/sites/site004
g, staffUser2016, staff001, /orgs/2/sites/site005
g, staffUser2017, staff001, /orgs/2/sites/site001
g, staffUser2017, staff001, /orgs/2/sites/site002
g, staffUser2017, staff001, /orgs/2/sites/site003
g, staffUser2017, staff001, /orgs/2/sites/site004
g, staffUser2017, staff001, /orgs/2/sites/site005
g, staffUser2018, staff001, /orgs/2/sites/site001
g, staffUser2018, staff001, /orgs/2/sites/site002
g, staffUser2018, staff001, /orgs/2/sites/site003
g, staffUser2018, staff001, /orgs/2/sites/site004
g, staffUser2018, staff001, /orgs/2/sites/site005
g, staffUser2019, staff001, /orgs/2/sites/site001
g, staffUser2019, staff001, /orgs/2/sites/site002
g, staffUser2019, staff001, /orgs/2/sites/site003
g, staffUser2019, staff001, /orgs/2/sites/site004
g, staffUser2019, staff001, /orgs/2/sites/site005
g, staffUser2020, staff001, /orgs/2/sites/site001
g, staffUser2020, staff001, /orgs/2/sites/site002
g, staffUser2020, staff001, /orgs/2/sites/site003
g, staffUser2020, staff001, /orgs/2/sites/site004
g, staffUser2020, staff001, /orgs/2/sites/site005
g, staffUser2021, staff001, /orgs/2/sites/site001
g, staffUser2021, staff001, /orgs/2/sites/site002
g, staffUser2021, staff001, /orgs/2/sites/site003
g, staffUser2021, staff001, /orgs/2/sites/site004
g, staffUser2021, staff001, /orgs/2/sites/site005
g, staffUser2022, staff001, /orgs/2/sites/site001
g, staffUser2022, staff001, /orgs/2/sites/site002
g, staffUser2022, staff001, /orgs/2/sites/site003
g, staffUser2022, staff001, /orgs/2/sites/site004
g, staffUser2022, staff001, /orgs/2/sites/site005
g, staffUser2023, staff001, /orgs/2/sites/site001
g, staffUser2023, staff001, /orgs/2/sites/site002
g, staffUser2023, staff001, /orgs/2/sites/site003
g, staffUser2023, staff001, /orgs/2/sites/site004
g, staffUser2023, staff001, /orgs/2/sites/site005
g, staffUser2024, staff001, /orgs/2/sites/site001
g, staffUser2024, staff001, /orgs/2/sites/site002
g, staffUser2024, staff001, /orgs/2/sites/site003
g, staffUser2024, staff001, /orgs/2/sites/site004
g, staffUser2024, staff001, /orgs/2/sites/site005
g, staffUser2025, staff001, /orgs/2/sites/site001
g, staffUser2025, staff001, /orgs/2/sites/site002
g, staffUser2025, staff001, /orgs/2/sites/site003
g, staffUser2025, staff001, /orgs/2/sites/site004
g, staffUser2025, staff001, /orgs/2/sites/site005
g, staffUser2026, staff001, /orgs/2/sites/site001
g, staffUser2026, staff001, /orgs/2/sites/site002
g, staffUser2026, staff001, /orgs/2/sites/site003
g, staffUser2026, staff001, /orgs/2/sites/site004
g, staffUser2026, staff001, /orgs/2/sites/site005
g, staffUser2027, staff001, /orgs/2/sites/site001
g, staffUser2027, staff001, /orgs/2/sites/site002
g, staffUser2027, staff001, /orgs/2/sites/site003
g, staffUser2027, staff001, /orgs/2/sites/site004
g, staffUser2027, staff001, /orgs/2/sites/site005
g, staffUser2028, staff001, /orgs/2/sites/site001
g, staffUser2028, staff001, /orgs/2/sites/site002
g, staffUser2028, staff001, /orgs/2/sites/site003
g, staffUser2028, staff001, /orgs/2/sites/site004
g, staffUser2028, staff001, /orgs/2/sites/site005
g, staffUser2029, staff001, /orgs/2/sites/site001
g, staffUser2029, staff001, /orgs/2/sites/site002
g, staffUser2029, staff001, /orgs/2/sites/site003
g, staffUser2029, staff001, /orgs/2/sites/site004
g, staffUser2029, staff001, /orgs/2/sites/site005
g, staffUser2030, staff001, /orgs/2/sites/site001
g, staffUser2030, staff001, /orgs/2/sites/site002
g, staffUser2030, staff001, /orgs/2/sites/site003
g, staffUser2030, staff001, /orgs/2/sites/site004
g, staffUser2030, staff001, /orgs/2/sites/site005
g, staffUser2031, staff001, /orgs/2/sites/site001
g, staffUser2031, staff001, /orgs/2/sites/site002
g, staffUser2031, staff001, /orgs/2/sites/site003
g, staffUser2031, staff001, /orgs/2/sites/site004
g, staffUser2031, staff001, /orgs/2/sites/site005
g, staffUser2032, staff001, /orgs/2/sites/site001
g, staffUser2032, staff001, /orgs/2/sites/site002
g, staffUser2032, staff001, /orgs/2/sites/site003
g, staffUser2032, staff001, /orgs/2/sites/site004
g, staffUser2032, staff001, /orgs/2/sites/site005
g, staffUser2033, staff001, /orgs/2/sites/site001
g, staffUser2033, staff001, /orgs/2/sites/site002
g, staffUser2033, staff001, /orgs/2/sites/site003
g, staffUser2033, staff001, /orgs/2/sites/site004
g, staffUser2033, staff001, /orgs/2/sites/site005
g, staffUser2034, staff001, /orgs/2/sites/site001
g, staffUser2034, staff001, /orgs/2/sites/site002
g, staffUser2034, staff001, /orgs/2/sites/site003
g, staffUser2034, staff001, /orgs/2/sites/site004
g, staffUser2034, staff001, /orgs/2/sites/site005
g, staffUser2035, staff001, /orgs/2/sites/site001
g, staffUser2035, staff001, /orgs/2/sites/site002
g, staffUser2035, staff001, /orgs/2/sites/site003
g, staffUser2035, staff001, /orgs/2/sites/site004
g, staffUser2035, staff001, /orgs/2/sites/site005
g, staffUser2036, staff001, /orgs/2/sites/site001
g, staffUser2036, staff001, /orgs/2/sites/site002
g, staffUser2036, staff001, /orgs/2/sites/site003
g, staffUser2036, staff001, /orgs/2/sites/site004
g, staffUser2036, staff001, /orgs/2/sites/site005
g, staffUser2037, staff001, /orgs/2/sites/site001
g, staffUser2037, staff001, /orgs/2/sites/site002
g, staffUser2037, staff001, /orgs/2/sites/site003
g, staffUser2037, staff001, /orgs/2/sites/site004
g, staffUser2037, staff001, /orgs/2/sites/site005
g, staffUser2038, staff001, /orgs/2/sites/site001
g, staffUser2038, staff001, /orgs/2/sites/site002
g, staffUser2038, staff001, /orgs/2/sites/site003
g, staffUser2038, staff001, /orgs/2/sites/site004
g, staffUser2038, staff001, /orgs/2/sites/site005
g, staffUser2039, staff001, /orgs/2/sites/site001
g, staffUser2039, staff001, /orgs/2/sites/site002
g, staffUser2039, staff001, /orgs/2/sites/site003
g, staffUser2039, staff001, /orgs/2/sites/site004
g, staffUser2039, staff001, /orgs/2/sites/site005
g, staffUser2040, staff001, /orgs/2/sites/site001
g, staffUser2040, staff001, /orgs/2/sites/site002
g, staffUser2040, staff001, /orgs/2/sites/site003
g, staffUser2040, staff001, /orgs/2/sites/site004
g, staffUser2040, staff001, /orgs/2/sites/site005
g, staffUser2041, staff001, /orgs/2/sites/site001
g, staffUser2041, staff001, /orgs/2/sites/site002
g, staffUser2041, staff001, /orgs/2/sites/site003
g, staffUser2041, staff001, /orgs/2/sites/site004
g, staffUser2041, staff001, /orgs/2/sites/site005
g, staffUser2042, staff001, /orgs/2/sites/site001
g, staffUser2042, staff001, /orgs/2/sites/site002
g, staffUser2042, staff001, /orgs/2/sites/site003
g, staffUser2042, staff001, /orgs/2/sites/site004
g, staffUser2042, staff001, /orgs/2/sites/site005
g, staffUser2043, staff001, /orgs/2/sites/site001
g, staffUser2043, staff001, /orgs/2/sites/site002
g, staffUser2043, staff001, /orgs/2/sites/site003
g, staffUser2043, staff001, /orgs/2/sites/site004
g, staffUser2043, staff001, /orgs/2/sites/site005
g, staffUser2044, staff001, /orgs/2/sites/site001
g, staffUser2044, staff001, /orgs/2/sites/site002
g, staffUser2044, staff001, /orgs/2/sites/site003
g, staffUser2044, staff001, /orgs/2/sites/site004
g, staffUser2044, staff001, /orgs/2/sites/site005
g, staffUser2045, staff001, /orgs/2/sites/site001
g, staffUser2045, staff001, /orgs/2/sites/site002
g, staffUser2045, staff001, /orgs/2/sites/site003
g, staffUser2045, staff001, /orgs/2/sites/site004
g, staffUser2045, staff001, /orgs/2/sites/site005
g, staffUser2046, staff001, /orgs/2/sites/site001
g, staffUser2046, staff001, /orgs/2/sites/site002
g, staffUser2046, staff001, /orgs/2/sites/site003
g, staffUser2046, staff001, /orgs/2/sites/site004
g, staffUser2046, staff001, /orgs/2/sites/site005
g, staffUser2047, staff001, /orgs/2/sites/site001
g, staffUser2047, staff001, /orgs/2/sites/site002
g, staffUser2047, staff001, /orgs/2/sites/site003
g, staffUser2047, staff001, /orgs/2/sites/site004
g, staffUser2047, staff001, /orgs/2/sites/site005
g, staffUser2048, staff001, /orgs/2/sites/site001
g, staffUser2048, staff001, /orgs/2/sites/site002
g, staffUser2048, staff001, /orgs/2/sites/site003
g, staffUser2048, staff001, /orgs/2/sites/site004
g, staffUser2048, staff001, /orgs/2/sites/site005
g, staffUser2049, staff001, /orgs/2/sites/site001
g, staffUser2049, staff001, /orgs/2/sites/site002
g, staffUser2049, staff001, /orgs/2/sites/site003
g, staffUser2049, staff001, /orgs/2/sites/site004
g, staffUser2049, staff001, /orgs/2/sites/site005
g, staffUser2050, staff001, /orgs/2/sites/site001
g, staffUser2050, staff001, /orgs/2/sites/site002
g, staffUser2050, staff001, /orgs/2/sites/site003
g, staffUser2050, staff001, /orgs/2/sites/site004
g, staffUser2050, staff001, /orgs/2/sites/site005
# Group - manager001, / org2
g, managerUser1001, manager001, /orgs/2/sites/site001
g, managerUser1001, manager001, /orgs/2/sites/site002
g, managerUser1001, manager001, /orgs/2/sites/site003
g, managerUser1001, manager001, /orgs/2/sites/site004
g, managerUser1001, manager001, /orgs/2/sites/site005
g, managerUser1001, manager001, /orgs/2/sites/site001
g, managerUser1001, manager001, /orgs/2/sites/site002
g, managerUser1001, manager001, /orgs/2/sites/site003
g, managerUser1001, manager001, /orgs/2/sites/site004
g, managerUser1001, manager001, /orgs/2/sites/site005
g, managerUser1003, manager001, /orgs/2/sites/site001
g, managerUser1003, manager001, /orgs/2/sites/site002
g, managerUser1003, manager001, /orgs/2/sites/site003
g, managerUser1003, manager001, /orgs/2/sites/site004
g, managerUser1003, manager001, /orgs/2/sites/site005
g, managerUser1004, manager001, /orgs/2/sites/site001
g, managerUser1004, manager001, /orgs/2/sites/site002
g, managerUser1004, manager001, /orgs/2/sites/site003
g, managerUser1004, manager001, /orgs/2/sites/site004
g, managerUser1004, manager001, /orgs/2/sites/site005
g, managerUser1005, manager001, /orgs/2/sites/site001
g, managerUser1005, manager001, /orgs/2/sites/site002
g, managerUser1005, manager001, /orgs/2/sites/site003
g, managerUser1005, manager001, /orgs/2/sites/site004
g, managerUser1005, manager001, /orgs/2/sites/site005
g, managerUser1006, manager001, /orgs/2/sites/site001
g, managerUser1006, manager001, /orgs/2/sites/site002
g, managerUser1006, manager001, /orgs/2/sites/site003
g, managerUser1006, manager001, /orgs/2/sites/site004
g, managerUser1006, manager001, /orgs/2/sites/site005
g, managerUser1007, manager001, /orgs/2/sites/site001
g, managerUser1007, manager001, /orgs/2/sites/site002
g, managerUser1007, manager001, /orgs/2/sites/site003
g, managerUser1007, manager001, /orgs/2/sites/site004
g, managerUser1007, manager001, /orgs/2/sites/site005
g, managerUser1008, manager001, /orgs/2/sites/site001
g, managerUser1008, manager001, /orgs/2/sites/site002
g, managerUser1008, manager001, /orgs/2/sites/site003
g, managerUser1008, manager001, /orgs/2/sites/site004
g, managerUser1008, manager001, /orgs/2/sites/site005
g, managerUser1009, manager001, /orgs/2/sites/site001
g, managerUser1009, manager001, /orgs/2/sites/site002
g, managerUser1009, manager001, /orgs/2/sites/site003
g, managerUser1009, manager001, /orgs/2/sites/site004
g, managerUser1009, manager001, /orgs/2/sites/site005
g, managerUser1010, manager001, /orgs/2/sites/site001
g, managerUser1010, manager001, /orgs/2/sites/site002
g, managerUser1010, manager001, /orgs/2/sites/site003
g, managerUser1010, manager001, /orgs/2/sites/site004
g, managerUser1010, manager001, /orgs/2/sites/site005
g, managerUser1011, manager001, /orgs/2/sites/site001
g, managerUser1011, manager001, /orgs/2/sites/site002
g, managerUser1011, manager001, /orgs/2/sites/site003
g, managerUser1011, manager001, /orgs/2/sites/site004
g, managerUser1011, manager001, /orgs/2/sites/site005
g, managerUser1012, manager001, /orgs/2/sites/site001
g, managerUser1012, manager001, /orgs/2/sites/site002
g, managerUser1012, manager001, /orgs/2/sites/site003
g, managerUser1012, manager001, /orgs/2/sites/site004
g, managerUser1012, manager001, /orgs/2/sites/site005
g, managerUser1013, manager001, /orgs/2/sites/site001
g, managerUser1013, manager001, /orgs/2/sites/site002
g, managerUser1013, manager001, /orgs/2/sites/site003
g, managerUser1013, manager001, /orgs/2/sites/site004
g, managerUser1013, manager001, /orgs/2/sites/site005
g, managerUser1014, manager001, /orgs/2/sites/site001
g, managerUser1014, manager001, /orgs/2/sites/site002
g, managerUser1014, manager001, /orgs/2/sites/site003
g, managerUser1014, manager001, /orgs/2/sites/site004
g, managerUser1014, manager001, /orgs/2/sites/site005
g, managerUser1015, manager001, /orgs/2/sites/site001
g, managerUser1015, manager001, /orgs/2/sites/site002
g, managerUser1015, manager001, /orgs/2/sites/site003
g, managerUser1015, manager001, /orgs/2/sites/site004
g, managerUser1015, manager001, /orgs/2/sites/site005
g, managerUser1016, manager001, /orgs/2/sites/site001
g, managerUser1016, manager001, /orgs/2/sites/site002
g, managerUser1016, manager001, /orgs/2/sites/site003
g, managerUser1016, manager001, /orgs/2/sites/site004
g, managerUser1016, manager001, /orgs/2/sites/site005
g, managerUser1017, manager001, /orgs/2/sites/site001
g, managerUser1017, manager001, /orgs/2/sites/site002
g, managerUser1017, manager001, /orgs/2/sites/site003
g, managerUser1017, manager001, /orgs/2/sites/site004
g, managerUser1017, manager001, /orgs/2/sites/site005
g, managerUser1018, manager001, /orgs/2/sites/site001
g, managerUser1018, manager001, /orgs/2/sites/site002
g, managerUser1018, manager001, /orgs/2/sites/site003
g, managerUser1018, manager001, /orgs/2/sites/site004
g, managerUser1018, manager001, /orgs/2/sites/site005
g, managerUser1019, manager001, /orgs/2/sites/site001
g, managerUser1019, manager001, /orgs/2/sites/site002
g, managerUser1019, manager001, /orgs/2/sites/site003
g, managerUser1019, manager001, /orgs/2/sites/site004
g, managerUser1019, manager001, /orgs/2/sites/site005
g, managerUser1020, manager001, /orgs/2/sites/site001
g, managerUser1020, manager001, /orgs/2/sites/site002
g, managerUser1020, manager001, /orgs/2/sites/site003
g, managerUser1020, manager001, /orgs/2/sites/site004
g, managerUser1020, manager001, /orgs/2/sites/site005
g, managerUser1021, manager001, /orgs/2/sites/site001
g, managerUser1021, manager001, /orgs/2/sites/site002
g, managerUser1021, manager001, /orgs/2/sites/site003
g, managerUser1021, manager001, /orgs/2/sites/site004
g, managerUser1021, manager001, /orgs/2/sites/site005
g, managerUser1022, manager001, /orgs/2/sites/site001
g, managerUser1022, manager001, /orgs/2/sites/site002
g, managerUser1022, manager001, /orgs/2/sites/site003
g, managerUser1022, manager001, /orgs/2/sites/site004
g, managerUser1022, manager001, /orgs/2/sites/site005
g, managerUser1023, manager001, /orgs/2/sites/site001
g, managerUser1023, manager001, /orgs/2/sites/site002
g, managerUser1023, manager001, /orgs/2/sites/site003
g, managerUser1023, manager001, /orgs/2/sites/site004
g, managerUser1023, manager001, /orgs/2/sites/site005
g, managerUser1024, manager001, /orgs/2/sites/site001
g, managerUser1024, manager001, /orgs/2/sites/site002
g, managerUser1024, manager001, /orgs/2/sites/site003
g, managerUser1024, manager001, /orgs/2/sites/site004
g, managerUser1024, manager001, /orgs/2/sites/site005
g, managerUser1025, manager001, /orgs/2/sites/site001
g, managerUser1025, manager001, /orgs/2/sites/site002
g, managerUser1025, manager001, /orgs/2/sites/site003
g, managerUser1025, manager001, /orgs/2/sites/site004
g, managerUser1025, manager001, /orgs/2/sites/site005
g, managerUser1026, manager001, /orgs/2/sites/site001
g, managerUser1026, manager001, /orgs/2/sites/site002
g, managerUser1026, manager001, /orgs/2/sites/site003
g, managerUser1026, manager001, /orgs/2/sites/site004
g, managerUser1026, manager001, /orgs/2/sites/site005
g, managerUser1027, manager001, /orgs/2/sites/site001
g, managerUser1027, manager001, /orgs/2/sites/site002
g, managerUser1027, manager001, /orgs/2/sites/site003
g, managerUser1027, manager001, /orgs/2/sites/site004
g, managerUser1027, manager001, /orgs/2/sites/site005
g, managerUser1028, manager001, /orgs/2/sites/site001
g, managerUser1028, manager001, /orgs/2/sites/site002
g, managerUser1028, manager001, /orgs/2/sites/site003
g, managerUser1028, manager001, /orgs/2/sites/site004
g, managerUser1028, manager001, /orgs/2/sites/site005
g, managerUser1029, manager001, /orgs/2/sites/site001
g, managerUser1029, manager001, /orgs/2/sites/site002
g, managerUser1029, manager001, /orgs/2/sites/site003
g, managerUser1029, manager001, /orgs/2/sites/site004
g, managerUser1029, manager001, /orgs/2/sites/site005
g, managerUser1030, manager001, /orgs/2/sites/site001
g, managerUser1030, manager001, /orgs/2/sites/site002
g, managerUser1030, manager001, /orgs/2/sites/site003
g, managerUser1030, manager001, /orgs/2/sites/site004
g, managerUser1030, manager001, /orgs/2/sites/site005
g, managerUser1031, manager001, /orgs/2/sites/site001
g, managerUser1031, manager001, /orgs/2/sites/site002
g, managerUser1031, manager001, /orgs/2/sites/site003
g, managerUser1031, manager001, /orgs/2/sites/site004
g, managerUser1031, manager001, /orgs/2/sites/site005
g, managerUser1032, manager001, /orgs/2/sites/site001
g, managerUser1032, manager001, /orgs/2/sites/site002
g, managerUser1032, manager001, /orgs/2/sites/site003
g, managerUser1032, manager001, /orgs/2/sites/site004
g, managerUser1032, manager001, /orgs/2/sites/site005
g, managerUser1033, manager001, /orgs/2/sites/site001
g, managerUser1033, manager001, /orgs/2/sites/site002
g, managerUser1033, manager001, /orgs/2/sites/site003
g, managerUser1033, manager001, /orgs/2/sites/site004
g, managerUser1033, manager001, /orgs/2/sites/site005
g, managerUser1034, manager001, /orgs/2/sites/site001
g, managerUser1034, manager001, /orgs/2/sites/site002
g, managerUser1034, manager001, /orgs/2/sites/site003
g, managerUser1034, manager001, /orgs/2/sites/site004
g, managerUser1034, manager001, /orgs/2/sites/site005
g, managerUser1035, manager001, /orgs/2/sites/site001
g, managerUser1035, manager001, /orgs/2/sites/site002
g, managerUser1035, manager001, /orgs/2/sites/site003
g, managerUser1035, manager001, /orgs/2/sites/site004
g, managerUser1035, manager001, /orgs/2/sites/site005
g, managerUser1036, manager001, /orgs/2/sites/site001
g, managerUser1036, manager001, /orgs/2/sites/site002
g, managerUser1036, manager001, /orgs/2/sites/site003
g, managerUser1036, manager001, /orgs/2/sites/site004
g, managerUser1036, manager001, /orgs/2/sites/site005
g, managerUser1037, manager001, /orgs/2/sites/site001
g, managerUser1037, manager001, /orgs/2/sites/site002
g, managerUser1037, manager001, /orgs/2/sites/site003
g, managerUser1037, manager001, /orgs/2/sites/site004
g, managerUser1037, manager001, /orgs/2/sites/site005
g, managerUser1038, manager001, /orgs/2/sites/site001
g, managerUser1038, manager001, /orgs/2/sites/site002
g, managerUser1038, manager001, /orgs/2/sites/site003
g, managerUser1038, manager001, /orgs/2/sites/site004
g, managerUser1038, manager001, /orgs/2/sites/site005
g, managerUser1039, manager001, /orgs/2/sites/site001
g, managerUser1039, manager001, /orgs/2/sites/site002
g, managerUser1039, manager001, /orgs/2/sites/site003
g, managerUser1039, manager001, /orgs/2/sites/site004
g, managerUser1039, manager001, /orgs/2/sites/site005
g, managerUser1040, manager001, /orgs/2/sites/site001
g, managerUser1040, manager001, /orgs/2/sites/site002
g, managerUser1040, manager001, /orgs/2/sites/site003
g, managerUser1040, manager001, /orgs/2/sites/site004
g, managerUser1040, manager001, /orgs/2/sites/site005
g, managerUser1041, manager001, /orgs/2/sites/site001
g, managerUser1041, manager001, /orgs/2/sites/site002
g, managerUser1041, manager001, /orgs/2/sites/site003
g, managerUser1041, manager001, /orgs/2/sites/site004
g, managerUser1041, manager001, /orgs/2/sites/site005
g, managerUser1042, manager001, /orgs/2/sites/site001
g, managerUser1042, manager001, /orgs/2/sites/site002
g, managerUser1042, manager001, /orgs/2/sites/site003
g, managerUser1042, manager001, /orgs/2/sites/site004
g, managerUser1042, manager001, /orgs/2/sites/site005
g, managerUser1043, manager001, /orgs/2/sites/site001
g, managerUser1043, manager001, /orgs/2/sites/site002
g, managerUser1043, manager001, /orgs/2/sites/site003
g, managerUser1043, manager001, /orgs/2/sites/site004
g, managerUser1043, manager001, /orgs/2/sites/site005
g, managerUser1044, manager001, /orgs/2/sites/site001
g, managerUser1044, manager001, /orgs/2/sites/site002
g, managerUser1044, manager001, /orgs/2/sites/site003
g, managerUser1044, manager001, /orgs/2/sites/site004
g, managerUser1044, manager001, /orgs/2/sites/site005
g, managerUser1045, manager001, /orgs/2/sites/site001
g, managerUser1045, manager001, /orgs/2/sites/site002
g, managerUser1045, manager001, /orgs/2/sites/site003
g, managerUser1045, manager001, /orgs/2/sites/site004
g, managerUser1045, manager001, /orgs/2/sites/site005
g, managerUser1046, manager001, /orgs/2/sites/site001
g, managerUser1046, manager001, /orgs/2/sites/site002
g, managerUser1046, manager001, /orgs/2/sites/site003
g, managerUser1046, manager001, /orgs/2/sites/site004
g, managerUser1046, manager001, /orgs/2/sites/site005
g, managerUser1047, manager001, /orgs/2/sites/site001
g, managerUser1047, manager001, /orgs/2/sites/site002
g, managerUser1047, manager001, /orgs/2/sites/site003
g, managerUser1047, manager001, /orgs/2/sites/site004
g, managerUser1047, manager001, /orgs/2/sites/site005
g, managerUser1048, manager001, /orgs/2/sites/site001
g, managerUser1048, manager001, /orgs/2/sites/site002
g, managerUser1048, manager001, /orgs/2/sites/site003
g, managerUser1048, manager001, /orgs/2/sites/site004
g, managerUser1048, manager001, /orgs/2/sites/site005
g, managerUser1049, manager001, /orgs/2/sites/site001
g, managerUser1049, manager001, /orgs/2/sites/site002
g, managerUser1049, manager001, /orgs/2/sites/site003
g, managerUser1049, manager001, /orgs/2/sites/site004
g, managerUser1049, manager001, /orgs/2/sites/site005
g, managerUser1050, manager001, /orgs/2/sites/site001
g, managerUser1050, manager001, /orgs/2/sites/site002
g, managerUser1050, manager001, /orgs/2/sites/site003
g, managerUser1050, manager001, /orgs/2/sites/site004
g, managerUser1050, manager001, /orgs/2/sites/site005
# Group - manager001, / org2
g, managerUser2001, manager001, /orgs/2/sites/site001
g, managerUser2001, manager001, /orgs/2/sites/site002
g, managerUser2001, manager001, /orgs/2/sites/site003
g, managerUser2001, manager001, /orgs/2/sites/site004
g, managerUser2001, manager001, /orgs/2/sites/site005
g, managerUser2001, manager001, /orgs/2/sites/site001
g, managerUser2001, manager001, /orgs/2/sites/site002
g, managerUser2001, manager001, /orgs/2/sites/site003
g, managerUser2001, manager001, /orgs/2/sites/site004
g, managerUser2001, manager001, /orgs/2/sites/site005
g, managerUser2003, manager001, /orgs/2/sites/site001
g, managerUser2003, manager001, /orgs/2/sites/site002
g, managerUser2003, manager001, /orgs/2/sites/site003
g, managerUser2003, manager001, /orgs/2/sites/site004
g, managerUser2003, manager001, /orgs/2/sites/site005
g, managerUser2004, manager001, /orgs/2/sites/site001
g, managerUser2004, manager001, /orgs/2/sites/site002
g, managerUser2004, manager001, /orgs/2/sites/site003
g, managerUser2004, manager001, /orgs/2/sites/site004
g, managerUser2004, manager001, /orgs/2/sites/site005
g, managerUser2005, manager001, /orgs/2/sites/site001
g, managerUser2005, manager001, /orgs/2/sites/site002
g, managerUser2005, manager001, /orgs/2/sites/site003
g, managerUser2005, manager001, /orgs/2/sites/site004
g, managerUser2005, manager001, /orgs/2/sites/site005
g, managerUser2006, manager001, /orgs/2/sites/site001
g, managerUser2006, manager001, /orgs/2/sites/site002
g, managerUser2006, manager001, /orgs/2/sites/site003
g, managerUser2006, manager001, /orgs/2/sites/site004
g, managerUser2006, manager001, /orgs/2/sites/site005
g, managerUser2007, manager001, /orgs/2/sites/site001
g, managerUser2007, manager001, /orgs/2/sites/site002
g, managerUser2007, manager001, /orgs/2/sites/site003
g, managerUser2007, manager001, /orgs/2/sites/site004
g, managerUser2007, manager001, /orgs/2/sites/site005
g, managerUser2008, manager001, /orgs/2/sites/site001
g, managerUser2008, manager001, /orgs/2/sites/site002
g, managerUser2008, manager001, /orgs/2/sites/site003
g, managerUser2008, manager001, /orgs/2/sites/site004
g, managerUser2008, manager001, /orgs/2/sites/site005
g, managerUser2009, manager001, /orgs/2/sites/site001
g, managerUser2009, manager001, /orgs/2/sites/site002
g, managerUser2009, manager001, /orgs/2/sites/site003
g, managerUser2009, manager001, /orgs/2/sites/site004
g, managerUser2009, manager001, /orgs/2/sites/site005
g, managerUser2010, manager001, /orgs/2/sites/site001
g, managerUser2010, manager001, /orgs/2/sites/site002
g, managerUser2010, manager001, /orgs/2/sites/site003
g, managerUser2010, manager001, /orgs/2/sites/site004
g, managerUser2010, manager001, /orgs/2/sites/site005
g, managerUser2011, manager001, /orgs/2/sites/site001
g, managerUser2011, manager001, /orgs/2/sites/site002
g, managerUser2011, manager001, /orgs/2/sites/site003
g, managerUser2011, manager001, /orgs/2/sites/site004
g, managerUser2011, manager001, /orgs/2/sites/site005
g, managerUser2012, manager001, /orgs/2/sites/site001
g, managerUser2012, manager001, /orgs/2/sites/site002
g, managerUser2012, manager001, /orgs/2/sites/site003
g, managerUser2012, manager001, /orgs/2/sites/site004
g, managerUser2012, manager001, /orgs/2/sites/site005
g, managerUser2013, manager001, /orgs/2/sites/site001
g, managerUser2013, manager001, /orgs/2/sites/site002
g, managerUser2013, manager001, /orgs/2/sites/site003
g, managerUser2013, manager001, /orgs/2/sites/site004
g, managerUser2013, manager001, /orgs/2/sites/site005
g, managerUser2014, manager001, /orgs/2/sites/site001
g, managerUser2014, manager001, /orgs/2/sites/site002
g, managerUser2014, manager001, /orgs/2/sites/site003
g, managerUser2014, manager001, /orgs/2/sites/site004
g, managerUser2014, manager001, /orgs/2/sites/site005
g, managerUser2015, manager001, /orgs/2/sites/site001
g, managerUser2015, manager001, /orgs/2/sites/site002
g, managerUser2015, manager001, /orgs/2/sites/site003
g, managerUser2015, manager001, /orgs/2/sites/site004
g, managerUser2015, manager001, /orgs/2/sites/site005
g, managerUser2016, manager001, /orgs/2/sites/site001
g, managerUser2016, manager001, /orgs/2/sites/site002
g, managerUser2016, manager001, /orgs/2/sites/site003
g, managerUser2016, manager001, /orgs/2/sites/site004
g, managerUser2016, manager001, /orgs/2/sites/site005
g, managerUser2017, manager001, /orgs/2/sites/site001
g, managerUser2017, manager001, /orgs/2/sites/site002
g, managerUser2017, manager001, /orgs/2/sites/site003
g, managerUser2017, manager001, /orgs/2/sites/site004
g, managerUser2017, manager001, /orgs/2/sites/site005
g, managerUser2018, manager001, /orgs/2/sites/site001
g, managerUser2018, manager001, /orgs/2/sites/site002
g, managerUser2018, manager001, /orgs/2/sites/site003
g, managerUser2018, manager001, /orgs/2/sites/site004
g, managerUser2018, manager001, /orgs/2/sites/site005
g, managerUser2019, manager001, /orgs/2/sites/site001
g, managerUser2019, manager001, /orgs/2/sites/site002
g, managerUser2019, manager001, /orgs/2/sites/site003
g, managerUser2019, manager001, /orgs/2/sites/site004
g, managerUser2019, manager001, /orgs/2/sites/site005
g, managerUser2020, manager001, /orgs/2/sites/site001
g, managerUser2020, manager001, /orgs/2/sites/site002
g, managerUser2020, manager001, /orgs/2/sites/site003
g, managerUser2020, manager001, /orgs/2/sites/site004
g, managerUser2020, manager001, /orgs/2/sites/site005
g, managerUser2021, manager001, /orgs/2/sites/site001
g, managerUser2021, manager001, /orgs/2/sites/site002
g, managerUser2021, manager001, /orgs/2/sites/site003
g, managerUser2021, manager001, /orgs/2/sites/site004
g, managerUser2021, manager001, /orgs/2/sites/site005
g, managerUser2022, manager001, /orgs/2/sites/site001
g, managerUser2022, manager001, /orgs/2/sites/site002
g, managerUser2022, manager001, /orgs/2/sites/site003
g, managerUser2022, manager001, /orgs/2/sites/site004
g, managerUser2022, manager001, /orgs/2/sites/site005
g, managerUser2023, manager001, /orgs/2/sites/site001
g, managerUser2023, manager001, /orgs/2/sites/site002
g, managerUser2023, manager001, /orgs/2/sites/site003
g, managerUser2023, manager001, /orgs/2/sites/site004
g, managerUser2023, manager001, /orgs/2/sites/site005
g, managerUser2024, manager001, /orgs/2/sites/site001
g, managerUser2024, manager001, /orgs/2/sites/site002
g, managerUser2024, manager001, /orgs/2/sites/site003
g, managerUser2024, manager001, /orgs/2/sites/site004
g, managerUser2024, manager001, /orgs/2/sites/site005
g, managerUser2025, manager001, /orgs/2/sites/site001
g, managerUser2025, manager001, /orgs/2/sites/site002
g, managerUser2025, manager001, /orgs/2/sites/site003
g, managerUser2025, manager001, /orgs/2/sites/site004
g, managerUser2025, manager001, /orgs/2/sites/site005
g, managerUser2026, manager001, /orgs/2/sites/site001
g, managerUser2026, manager001, /orgs/2/sites/site002
g, managerUser2026, manager001, /orgs/2/sites/site003
g, managerUser2026, manager001, /orgs/2/sites/site004
g, managerUser2026, manager001, /orgs/2/sites/site005
g, managerUser2027, manager001, /orgs/2/sites/site001
g, managerUser2027, manager001, /orgs/2/sites/site002
g, managerUser2027, manager001, /orgs/2/sites/site003
g, managerUser2027, manager001, /orgs/2/sites/site004
g, managerUser2027, manager001, /orgs/2/sites/site005
g, managerUser2028, manager001, /orgs/2/sites/site001
g, managerUser2028, manager001, /orgs/2/sites/site002
g, managerUser2028, manager001, /orgs/2/sites/site003
g, managerUser2028, manager001, /orgs/2/sites/site004
g, managerUser2028, manager001, /orgs/2/sites/site005
g, managerUser2029, manager001, /orgs/2/sites/site001
g, managerUser2029, manager001, /orgs/2/sites/site002
g, managerUser2029, manager001, /orgs/2/sites/site003
g, managerUser2029, manager001, /orgs/2/sites/site004
g, managerUser2029, manager001, /orgs/2/sites/site005
g, managerUser2030, manager001, /orgs/2/sites/site001
g, managerUser2030, manager001, /orgs/2/sites/site002
g, managerUser2030, manager001, /orgs/2/sites/site003
g, managerUser2030, manager001, /orgs/2/sites/site004
g, managerUser2030, manager001, /orgs/2/sites/site005
g, managerUser2031, manager001, /orgs/2/sites/site001
g, managerUser2031, manager001, /orgs/2/sites/site002
g, managerUser2031, manager001, /orgs/2/sites/site003
g, managerUser2031, manager001, /orgs/2/sites/site004
g, managerUser2031, manager001, /orgs/2/sites/site005
g, managerUser2032, manager001, /orgs/2/sites/site001
g, managerUser2032, manager001, /orgs/2/sites/site002
g, managerUser2032, manager001, /orgs/2/sites/site003
g, managerUser2032, manager001, /orgs/2/sites/site004
g, managerUser2032, manager001, /orgs/2/sites/site005
g, managerUser2033, manager001, /orgs/2/sites/site001
g, managerUser2033, manager001, /orgs/2/sites/site002
g, managerUser2033, manager001, /orgs/2/sites/site003
g, managerUser2033, manager001, /orgs/2/sites/site004
g, managerUser2033, manager001, /orgs/2/sites/site005
g, managerUser2034, manager001, /orgs/2/sites/site001
g, managerUser2034, manager001, /orgs/2/sites/site002
g, managerUser2034, manager001, /orgs/2/sites/site003
g, managerUser2034, manager001, /orgs/2/sites/site004
g, managerUser2034, manager001, /orgs/2/sites/site005
g, managerUser2035, manager001, /orgs/2/sites/site001
g, managerUser2035, manager001, /orgs/2/sites/site002
g, managerUser2035, manager001, /orgs/2/sites/site003
g, managerUser2035, manager001, /orgs/2/sites/site004
g, managerUser2035, manager001, /orgs/2/sites/site005
g, managerUser2036, manager001, /orgs/2/sites/site001
g, managerUser2036, manager001, /orgs/2/sites/site002
g, managerUser2036, manager001, /orgs/2/sites/site003
g, managerUser2036, manager001, /orgs/2/sites/site004
g, managerUser2036, manager001, /orgs/2/sites/site005
g, managerUser2037, manager001, /orgs/2/sites/site001
g, managerUser2037, manager001, /orgs/2/sites/site002
g, managerUser2037, manager001, /orgs/2/sites/site003
g, managerUser2037, manager001, /orgs/2/sites/site004
g, managerUser2037, manager001, /orgs/2/sites/site005
g, managerUser2038, manager001, /orgs/2/sites/site001
g, managerUser2038, manager001, /orgs/2/sites/site002
g, managerUser2038, manager001, /orgs/2/sites/site003
g, managerUser2038, manager001, /orgs/2/sites/site004
g, managerUser2038, manager001, /orgs/2/sites/site005
g, managerUser2039, manager001, /orgs/2/sites/site001
g, managerUser2039, manager001, /orgs/2/sites/site002
g, managerUser2039, manager001, /orgs/2/sites/site003
g, managerUser2039, manager001, /orgs/2/sites/site004
g, managerUser2039, manager001, /orgs/2/sites/site005
g, managerUser2040, manager001, /orgs/2/sites/site001
g, managerUser2040, manager001, /orgs/2/sites/site002
g, managerUser2040, manager001, /orgs/2/sites/site003
g, managerUser2040, manager001, /orgs/2/sites/site004
g, managerUser2040, manager001, /orgs/2/sites/site005
g, managerUser2041, manager001, /orgs/2/sites/site001
g, managerUser2041, manager001, /orgs/2/sites/site002
g, managerUser2041, manager001, /orgs/2/sites/site003
g, managerUser2041, manager001, /orgs/2/sites/site004
g, managerUser2041, manager001, /orgs/2/sites/site005
g, managerUser2042, manager001, /orgs/2/sites/site001
g, managerUser2042, manager001, /orgs/2/sites/site002
g, managerUser2042, manager001, /orgs/2/sites/site003
g, managerUser2042, manager001, /orgs/2/sites/site004
g, managerUser2042, manager001, /orgs/2/sites/site005
g, managerUser2043, manager001, /orgs/2/sites/site001
g, managerUser2043, manager001, /orgs/2/sites/site002
g, managerUser2043, manager001, /orgs/2/sites/site003
g, managerUser2043, manager001, /orgs/2/sites/site004
g, managerUser2043, manager001, /orgs/2/sites/site005
g, managerUser2044, manager001, /orgs/2/sites/site001
g, managerUser2044, manager001, /orgs/2/sites/site002
g, managerUser2044, manager001, /orgs/2/sites/site003
g, managerUser2044, manager001, /orgs/2/sites/site004
g, managerUser2044, manager001, /orgs/2/sites/site005
g, managerUser2045, manager001, /orgs/2/sites/site001
g, managerUser2045, manager001, /orgs/2/sites/site002
g, managerUser2045, manager001, /orgs/2/sites/site003
g, managerUser2045, manager001, /orgs/2/sites/site004
g, managerUser2045, manager001, /orgs/2/sites/site005
g, managerUser2046, manager001, /orgs/2/sites/site001
g, managerUser2046, manager001, /orgs/2/sites/site002
g, managerUser2046, manager001, /orgs/2/sites/site003
g, managerUser2046, manager001, /orgs/2/sites/site004
g, managerUser2046, manager001, /orgs/2/sites/site005
g, managerUser2047, manager001, /orgs/2/sites/site001
g, managerUser2047, manager001, /orgs/2/sites/site002
g, managerUser2047, manager001, /orgs/2/sites/site003
g, managerUser2047, manager001, /orgs/2/sites/site004
g, managerUser2047, manager001, /orgs/2/sites/site005
g, managerUser2048, manager001, /orgs/2/sites/site001
g, managerUser2048, manager001, /orgs/2/sites/site002
g, managerUser2048, manager001, /orgs/2/sites/site003
g, managerUser2048, manager001, /orgs/2/sites/site004
g, managerUser2048, manager001, /orgs/2/sites/site005
g, managerUser2049, manager001, /orgs/2/sites/site001
g, managerUser2049, manager001, /orgs/2/sites/site002
g, managerUser2049, manager001, /orgs/2/sites/site003
g, managerUser2049, manager001, /orgs/2/sites/site004
g, managerUser2049, manager001, /orgs/2/sites/site005
g, managerUser2050, manager001, /orgs/2/sites/site001
g, managerUser2050, manager001, /orgs/2/sites/site002
g, managerUser2050, manager001, /orgs/2/sites/site003
g, managerUser2050, manager001, /orgs/2/sites/site004
g, managerUser2050, manager001, /orgs/2/sites/site005
# Group - customer001, / org2
g, customerUser1001, customer001, /orgs/2/sites/site001
g, customerUser1001, customer001, /orgs/2/sites/site002
g, customerUser1001, customer001, /orgs/2/sites/site003
g, customerUser1001, customer001, /orgs/2/sites/site004
g, customerUser1001, customer001, /orgs/2/sites/site005
g, customerUser1001, customer001, /orgs/2/sites/site001
g, customerUser1001, customer001, /orgs/2/sites/site002
g, customerUser1001, customer001, /orgs/2/sites/site003
g, customerUser1001, customer001, /orgs/2/sites/site004
g, customerUser1001, customer001, /orgs/2/sites/site005
g, customerUser1003, customer001, /orgs/2/sites/site001
g, customerUser1003, customer001, /orgs/2/sites/site002
g, customerUser1003, customer001, /orgs/2/sites/site003
g, customerUser1003, customer001, /orgs/2/sites/site004
g, customerUser1003, customer001, /orgs/2/sites/site005
g, customerUser1004, customer001, /orgs/2/sites/site001
g, customerUser1004, customer001, /orgs/2/sites/site002
g, customerUser1004, customer001, /orgs/2/sites/site003
g, customerUser1004, customer001, /orgs/2/sites/site004
g, customerUser1004, customer001, /orgs/2/sites/site005
g, customerUser1005, customer001, /orgs/2/sites/site001
g, customerUser1005, customer001, /orgs/2/sites/site002
g, customerUser1005, customer001, /orgs/2/sites/site003
g, customerUser1005, customer001, /orgs/2/sites/site004
g, customerUser1005, customer001, /orgs/2/sites/site005
g, customerUser1006, customer001, /orgs/2/sites/site001
g, customerUser1006, customer001, /orgs/2/sites/site002
g, customerUser1006, customer001, /orgs/2/sites/site003
g, customerUser1006, customer001, /orgs/2/sites/site004
g, customerUser1006, customer001, /orgs/2/sites/site005
g, customerUser1007, customer001, /orgs/2/sites/site001
g, customerUser1007, customer001, /orgs/2/sites/site002
g, customerUser1007, customer001, /orgs/2/sites/site003
g, customerUser1007, customer001, /orgs/2/sites/site004
g, customerUser1007, customer001, /orgs/2/sites/site005
g, customerUser1008, customer001, /orgs/2/sites/site001
g, customerUser1008, customer001, /orgs/2/sites/site002
g, customerUser1008, customer001, /orgs/2/sites/site003
g, customerUser1008, customer001, /orgs/2/sites/site004
g, customerUser1008, customer001, /orgs/2/sites/site005
g, customerUser1009, customer001, /orgs/2/sites/site001
g, customerUser1009, customer001, /orgs/2/sites/site002
g, customerUser1009, customer001, /orgs/2/sites/site003
g, customerUser1009, customer001, /orgs/2/sites/site004
g, customerUser1009, customer001, /orgs/2/sites/site005
g, customerUser1010, customer001, /orgs/2/sites/site001
g, customerUser1010, customer001, /orgs/2/sites/site002
g, customerUser1010, customer001, /orgs/2/sites/site003
g, customerUser1010, customer001, /orgs/2/sites/site004
g, customerUser1010, customer001, /orgs/2/sites/site005
g, customerUser1011, customer001, /orgs/2/sites/site001
g, customerUser1011, customer001, /orgs/2/sites/site002
g, customerUser1011, customer001, /orgs/2/sites/site003
g, customerUser1011, customer001, /orgs/2/sites/site004
g, customerUser1011, customer001, /orgs/2/sites/site005
g, customerUser1012, customer001, /orgs/2/sites/site001
g, customerUser1012, customer001, /orgs/2/sites/site002
g, customerUser1012, customer001, /orgs/2/sites/site003
g, customerUser1012, customer001, /orgs/2/sites/site004
g, customerUser1012, customer001, /orgs/2/sites/site005
g, customerUser1013, customer001, /orgs/2/sites/site001
g, customerUser1013, customer001, /orgs/2/sites/site002
g, customerUser1013, customer001, /orgs/2/sites/site003
g, customerUser1013, customer001, /orgs/2/sites/site004
g, customerUser1013, customer001, /orgs/2/sites/site005
g, customerUser1014, customer001, /orgs/2/sites/site001
g, customerUser1014, customer001, /orgs/2/sites/site002
g, customerUser1014, customer001, /orgs/2/sites/site003
g, customerUser1014, customer001, /orgs/2/sites/site004
g, customerUser1014, customer001, /orgs/2/sites/site005
g, customerUser1015, customer001, /orgs/2/sites/site001
g, customerUser1015, customer001, /orgs/2/sites/site002
g, customerUser1015, customer001, /orgs/2/sites/site003
g, customerUser1015, customer001, /orgs/2/sites/site004
g, customerUser1015, customer001, /orgs/2/sites/site005
g, customerUser1016, customer001, /orgs/2/sites/site001
g, customerUser1016, customer001, /orgs/2/sites/site002
g, customerUser1016, customer001, /orgs/2/sites/site003
g, customerUser1016, customer001, /orgs/2/sites/site004
g, customerUser1016, customer001, /orgs/2/sites/site005
g, customerUser1017, customer001, /orgs/2/sites/site001
g, customerUser1017, customer001, /orgs/2/sites/site002
g, customerUser1017, customer001, /orgs/2/sites/site003
g, customerUser1017, customer001, /orgs/2/sites/site004
g, customerUser1017, customer001, /orgs/2/sites/site005
g, customerUser1018, customer001, /orgs/2/sites/site001
g, customerUser1018, customer001, /orgs/2/sites/site002
g, customerUser1018, customer001, /orgs/2/sites/site003
g, customerUser1018, customer001, /orgs/2/sites/site004
g, customerUser1018, customer001, /orgs/2/sites/site005
g, customerUser1019, customer001, /orgs/2/sites/site001
g, customerUser1019, customer001, /orgs/2/sites/site002
g, customerUser1019, customer001, /orgs/2/sites/site003
g, customerUser1019, customer001, /orgs/2/sites/site004
g, customerUser1019, customer001, /orgs/2/sites/site005
g, customerUser1020, customer001, /orgs/2/sites/site001
g, customerUser1020, customer001, /orgs/2/sites/site002
g, customerUser1020, customer001, /orgs/2/sites/site003
g, customerUser1020, customer001, /orgs/2/sites/site004
g, customerUser1020, customer001, /orgs/2/sites/site005
g, customerUser1021, customer001, /orgs/2/sites/site001
g, customerUser1021, customer001, /orgs/2/sites/site002
g, customerUser1021, customer001, /orgs/2/sites/site003
g, customerUser1021, customer001, /orgs/2/sites/site004
g, customerUser1021, customer001, /orgs/2/sites/site005
g, customerUser1022, customer001, /orgs/2/sites/site001
g, customerUser1022, customer001, /orgs/2/sites/site002
g, customerUser1022, customer001, /orgs/2/sites/site003
g, customerUser1022, customer001, /orgs/2/sites/site004
g, customerUser1022, customer001, /orgs/2/sites/site005
g, customerUser1023, customer001, /orgs/2/sites/site001
g, customerUser1023, customer001, /orgs/2/sites/site002
g, customerUser1023, customer001, /orgs/2/sites/site003
g, customerUser1023, customer001, /orgs/2/sites/site004
g, customerUser1023, customer001, /orgs/2/sites/site005
g, customerUser1024, customer001, /orgs/2/sites/site001
g, customerUser1024, customer001, /orgs/2/sites/site002
g, customerUser1024, customer001, /orgs/2/sites/site003
g, customerUser1024, customer001, /orgs/2/sites/site004
g, customerUser1024, customer001, /orgs/2/sites/site005
g, customerUser1025, customer001, /orgs/2/sites/site001
g, customerUser1025, customer001, /orgs/2/sites/site002
g, customerUser1025, customer001, /orgs/2/sites/site003
g, customerUser1025, customer001, /orgs/2/sites/site004
g, customerUser1025, customer001, /orgs/2/sites/site005
g, customerUser1026, customer001, /orgs/2/sites/site001
g, customerUser1026, customer001, /orgs/2/sites/site002
g, customerUser1026, customer001, /orgs/2/sites/site003
g, customerUser1026, customer001, /orgs/2/sites/site004
g, customerUser1026, customer001, /orgs/2/sites/site005
g, customerUser1027, customer001, /orgs/2/sites/site001
g, customerUser1027, customer001, /orgs/2/sites/site002
g, customerUser1027, customer001, /orgs/2/sites/site003
g, customerUser1027, customer001, /orgs/2/sites/site004
g, customerUser1027, customer001, /orgs/2/sites/site005
g, customerUser1028, customer001, /orgs/2/sites/site001
g, customerUser1028, customer001, /orgs/2/sites/site002
g, customerUser1028, customer001, /orgs/2/sites/site003
g, customerUser1028, customer001, /orgs/2/sites/site004
g, customerUser1028, customer001, /orgs/2/sites/site005
g, customerUser1029, customer001, /orgs/2/sites/site001
g, customerUser1029, customer001, /orgs/2/sites/site002
g, customerUser1029, customer001, /orgs/2/sites/site003
g, customerUser1029, customer001, /orgs/2/sites/site004
g, customerUser1029, customer001, /orgs/2/sites/site005
g, customerUser1030, customer001, /orgs/2/sites/site001
g, customerUser1030, customer001, /orgs/2/sites/site002
g, customerUser1030, customer001, /orgs/2/sites/site003
g, customerUser1030, customer001, /orgs/2/sites/site004
g, customerUser1030, customer001, /orgs/2/sites/site005
g, customerUser1031, customer001, /orgs/2/sites/site001
g, customerUser1031, customer001, /orgs/2/sites/site002
g, customerUser1031, customer001, /orgs/2/sites/site003
g, customerUser1031, customer001, /orgs/2/sites/site004
g, customerUser1031, customer001, /orgs/2/sites/site005
g, customerUser1032, customer001, /orgs/2/sites/site001
g, customerUser1032, customer001, /orgs/2/sites/site002
g, customerUser1032, customer001, /orgs/2/sites/site003
g, customerUser1032, customer001, /orgs/2/sites/site004
g, customerUser1032, customer001, /orgs/2/sites/site005
g, customerUser1033, customer001, /orgs/2/sites/site001
g, customerUser1033, customer001, /orgs/2/sites/site002
g, customerUser1033, customer001, /orgs/2/sites/site003
g, customerUser1033, customer001, /orgs/2/sites/site004
g, customerUser1033, customer001, /orgs/2/sites/site005
g, customerUser1034, customer001, /orgs/2/sites/site001
g, customerUser1034, customer001, /orgs/2/sites/site002
g, customerUser1034, customer001, /orgs/2/sites/site003
g, customerUser1034, customer001, /orgs/2/sites/site004
g, customerUser1034, customer001, /orgs/2/sites/site005
g, customerUser1035, customer001, /orgs/2/sites/site001
g, customerUser1035, customer001, /orgs/2/sites/site002
g, customerUser1035, customer001, /orgs/2/sites/site003
g, customerUser1035, customer001, /orgs/2/sites/site004
g, customerUser1035, customer001, /orgs/2/sites/site005
g, customerUser1036, customer001, /orgs/2/sites/site001
g, customerUser1036, customer001, /orgs/2/sites/site002
g, customerUser1036, customer001, /orgs/2/sites/site003
g, customerUser1036, customer001, /orgs/2/sites/site004
g, customerUser1036, customer001, /orgs/2/sites/site005
g, customerUser1037, customer001, /orgs/2/sites/site001
g, customerUser1037, customer001, /orgs/2/sites/site002
g, customerUser1037, customer001, /orgs/2/sites/site003
g, customerUser1037, customer001, /orgs/2/sites/site004
g, customerUser1037, customer001, /orgs/2/sites/site005
g, customerUser1038, customer001, /orgs/2/sites/site001
g, customerUser1038, customer001, /orgs/2/sites/site002
g, customerUser1038, customer001, /orgs/2/sites/site003
g, customerUser1038, customer001, /orgs/2/sites/site004
g, customerUser1038, customer001, /orgs/2/sites/site005
g, customerUser1039, customer001, /orgs/2/sites/site001
g, customerUser1039, customer001, /orgs/2/sites/site002
g, customerUser1039, customer001, /orgs/2/sites/site003
g, customerUser1039, customer001, /orgs/2/sites/site004
g, customerUser1039, customer001, /orgs/2/sites/site005
g, customerUser1040, customer001, /orgs/2/sites/site001
g, customerUser1040, customer001, /orgs/2/sites/site002
g, customerUser1040, customer001, /orgs/2/sites/site003
g, customerUser1040, customer001, /orgs/2/sites/site004
g, customerUser1040, customer001, /orgs/2/sites/site005
g, customerUser1041, customer001, /orgs/2/sites/site001
g, customerUser1041, customer001, /orgs/2/sites/site002
g, customerUser1041, customer001, /orgs/2/sites/site003
g, customerUser1041, customer001, /orgs/2/sites/site004
g, customerUser1041, customer001, /orgs/2/sites/site005
g, customerUser1042, customer001, /orgs/2/sites/site001
g, customerUser1042, customer001, /orgs/2/sites/site002
g, customerUser1042, customer001, /orgs/2/sites/site003
g, customerUser1042, customer001, /orgs/2/sites/site004
g, customerUser1042, customer001, /orgs/2/sites/site005
g, customerUser1043, customer001, /orgs/2/sites/site001
g, customerUser1043, customer001, /orgs/2/sites/site002
g, customerUser1043, customer001, /orgs/2/sites/site003
g, customerUser1043, customer001, /orgs/2/sites/site004
g, customerUser1043, customer001, /orgs/2/sites/site005
g, customerUser1044, customer001, /orgs/2/sites/site001
g, customerUser1044, customer001, /orgs/2/sites/site002
g, customerUser1044, customer001, /orgs/2/sites/site003
g, customerUser1044, customer001, /orgs/2/sites/site004
g, customerUser1044, customer001, /orgs/2/sites/site005
g, customerUser1045, customer001, /orgs/2/sites/site001
g, customerUser1045, customer001, /orgs/2/sites/site002
g, customerUser1045, customer001, /orgs/2/sites/site003
g, customerUser1045, customer001, /orgs/2/sites/site004
g, customerUser1045, customer001, /orgs/2/sites/site005
g, customerUser1046, customer001, /orgs/2/sites/site001
g, customerUser1046, customer001, /orgs/2/sites/site002
g, customerUser1046, customer001, /orgs/2/sites/site003
g, customerUser1046, customer001, /orgs/2/sites/site004
g, customerUser1046, customer001, /orgs/2/sites/site005
g, customerUser1047, customer001, /orgs/2/sites/site001
g, customerUser1047, customer001, /orgs/2/sites/site002
g, customerUser1047, customer001, /orgs/2/sites/site003
g, customerUser1047, customer001, /orgs/2/sites/site004
g, customerUser1047, customer001, /orgs/2/sites/site005
g, customerUser1048, customer001, /orgs/2/sites/site001
g, customerUser1048, customer001, /orgs/2/sites/site002
g, customerUser1048, customer001, /orgs/2/sites/site003
g, customerUser1048, customer001, /orgs/2/sites/site004
g, customerUser1048, customer001, /orgs/2/sites/site005
g, customerUser1049, customer001, /orgs/2/sites/site001
g, customerUser1049, customer001, /orgs/2/sites/site002
g, customerUser1049, customer001, /orgs/2/sites/site003
g, customerUser1049, customer001, /orgs/2/sites/site004
g, customerUser1049, customer001, /orgs/2/sites/site005
g, customerUser1050, customer001, /orgs/2/sites/site001
g, customerUser1050, customer001, /orgs/2/sites/site002
g, customerUser1050, customer001, /orgs/2/sites/site003
g, customerUser1050, customer001, /orgs/2/sites/site004
g, customerUser1050, customer001, /orgs/2/sites/site005
# Group - customer001, / org2
g, customerUser2001, customer001, /orgs/2/sites/site001
g, customerUser2001, customer001, /orgs/2/sites/site002
g, customerUser2001, customer001, /orgs/2/sites/site003
g, customerUser2001, customer001, /orgs/2/sites/site004
g, customerUser2001, customer001, /orgs/2/sites/site005
g, customerUser2001, customer001, /orgs/2/sites/site001
g, customerUser2001, customer001, /orgs/2/sites/site002
g, customerUser2001, customer001, /orgs/2/sites/site003
g, customerUser2001, customer001, /orgs/2/sites/site004
g, customerUser2001, customer001, /orgs/2/sites/site005
g, customerUser2003, customer001, /orgs/2/sites/site001
g, customerUser2003, customer001, /orgs/2/sites/site002
g, customerUser2003, customer001, /orgs/2/sites/site003
g, customerUser2003, customer001, /orgs/2/sites/site004
g, customerUser2003, customer001, /orgs/2/sites/site005
g, customerUser2004, customer001, /orgs/2/sites/site001
g, customerUser2004, customer001, /orgs/2/sites/site002
g, customerUser2004, customer001, /orgs/2/sites/site003
g, customerUser2004, customer001, /orgs/2/sites/site004
g, customerUser2004, customer001, /orgs/2/sites/site005
g, customerUser2005, customer001, /orgs/2/sites/site001
g, customerUser2005, customer001, /orgs/2/sites/site002
g, customerUser2005, customer001, /orgs/2/sites/site003
g, customerUser2005, customer001, /orgs/2/sites/site004
g, customerUser2005, customer001, /orgs/2/sites/site005
g, customerUser2006, customer001, /orgs/2/sites/site001
g, customerUser2006, customer001, /orgs/2/sites/site002
g, customerUser2006, customer001, /orgs/2/sites/site003
g, customerUser2006, customer001, /orgs/2/sites/site004
g, customerUser2006, customer001, /orgs/2/sites/site005
g, customerUser2007, customer001, /orgs/2/sites/site001
g, customerUser2007, customer001, /orgs/2/sites/site002
g, customerUser2007, customer001, /orgs/2/sites/site003
g, customerUser2007, customer001, /orgs/2/sites/site004
g, customerUser2007, customer001, /orgs/2/sites/site005
g, customerUser2008, customer001, /orgs/2/sites/site001
g, customerUser2008, customer001, /orgs/2/sites/site002
g, customerUser2008, customer001, /orgs/2/sites/site003
g, customerUser2008, customer001, /orgs/2/sites/site004
g, customerUser2008, customer001, /orgs/2/sites/site005
g, customerUser2009, customer001, /orgs/2/sites/site001
g, customerUser2009, customer001, /orgs/2/sites/site002
g, customerUser2009, customer001, /orgs/2/sites/site003
g, customerUser2009, customer001, /orgs/2/sites/site004
g, customerUser2009, customer001, /orgs/2/sites/site005
g, customerUser2010, customer001, /orgs/2/sites/site001
g, customerUser2010, customer001, /orgs/2/sites/site002
g, customerUser2010, customer001, /orgs/2/sites/site003
g, customerUser2010, customer001, /orgs/2/sites/site004
g, customerUser2010, customer001, /orgs/2/sites/site005
g, customerUser2011, customer001, /orgs/2/sites/site001
g, customerUser2011, customer001, /orgs/2/sites/site002
g, customerUser2011, customer001, /orgs/2/sites/site003
g, customerUser2011, customer001, /orgs/2/sites/site004
g, customerUser2011, customer001, /orgs/2/sites/site005
g, customerUser2012, customer001, /orgs/2/sites/site001
g, customerUser2012, customer001, /orgs/2/sites/site002
g, customerUser2012, customer001, /orgs/2/sites/site003
g, customerUser2012, customer001, /orgs/2/sites/site004
g, customerUser2012, customer001, /orgs/2/sites/site005
g, customerUser2013, customer001, /orgs/2/sites/site001
g, customerUser2013, customer001, /orgs/2/sites/site002
g, customerUser2013, customer001, /orgs/2/sites/site003
g, customerUser2013, customer001, /orgs/2/sites/site004
g, customerUser2013, customer001, /orgs/2/sites/site005
g, customerUser2014, customer001, /orgs/2/sites/site001
g, customerUser2014, customer001, /orgs/2/sites/site002
g, customerUser2014, customer001, /orgs/2/sites/site003
g, customerUser2014, customer001, /orgs/2/sites/site004
g, customerUser2014, customer001, /orgs/2/sites/site005
g, customerUser2015, customer001, /orgs/2/sites/site001
g, customerUser2015, customer001, /orgs/2/sites/site002
g, customerUser2015, customer001, /orgs/2/sites/site003
g, customerUser2015, customer001, /orgs/2/sites/site004
g, customerUser2015, customer001, /orgs/2/sites/site005
g, customerUser2016, customer001, /orgs/2/sites/site001
g, customerUser2016, customer001, /orgs/2/sites/site002
g, customerUser2016, customer001, /orgs/2/sites/site003
g, customerUser2016, customer001, /orgs/2/sites/site004
g, customerUser2016, customer001, /orgs/2/sites/site005
g, customerUser2017, customer001, /orgs/2/sites/site001
g, customerUser2017, customer001, /orgs/2/sites/site002
g, customerUser2017, customer001, /orgs/2/sites/site003
g, customerUser2017, customer001, /orgs/2/sites/site004
g, customerUser2017, customer001, /orgs/2/sites/site005
g, customerUser2018, customer001, /orgs/2/sites/site001
g, customerUser2018, customer001, /orgs/2/sites/site002
g, customerUser2018, customer001, /orgs/2/sites/site003
g, customerUser2018, customer001, /orgs/2/sites/site004
g, customerUser2018, customer001, /orgs/2/sites/site005
g, customerUser2019, customer001, /orgs/2/sites/site001
g, customerUser2019, customer001, /orgs/2/sites/site002
g, customerUser2019, customer001, /orgs/2/sites/site003
g, customerUser2019, customer001, /orgs/2/sites/site004
g, customerUser2019, customer001, /orgs/2/sites/site005
g, customerUser2020, customer001, /orgs/2/sites/site001
g, customerUser2020, customer001, /orgs/2/sites/site002
g, customerUser2020, customer001, /orgs/2/sites/site003
g, customerUser2020, customer001, /orgs/2/sites/site004
g, customerUser2020, customer001, /orgs/2/sites/site005
g, customerUser2021, customer001, /orgs/2/sites/site001
g, customerUser2021, customer001, /orgs/2/sites/site002
g, customerUser2021, customer001, /orgs/2/sites/site003
g, customerUser2021, customer001, /orgs/2/sites/site004
g, customerUser2021, customer001, /orgs/2/sites/site005
g, customerUser2022, customer001, /orgs/2/sites/site001
g, customerUser2022, customer001, /orgs/2/sites/site002
g, customerUser2022, customer001, /orgs/2/sites/site003
g, customerUser2022, customer001, /orgs/2/sites/site004
g, customerUser2022, customer001, /orgs/2/sites/site005
g, customerUser2023, customer001, /orgs/2/sites/site001
g, customerUser2023, customer001, /orgs/2/sites/site002
g, customerUser2023, customer001, /orgs/2/sites/site003
g, customerUser2023, customer001, /orgs/2/sites/site004
g, customerUser2023, customer001, /orgs/2/sites/site005
g, customerUser2024, customer001, /orgs/2/sites/site001
g, customerUser2024, customer001, /orgs/2/sites/site002
g, customerUser2024, customer001, /orgs/2/sites/site003
g, customerUser2024, customer001, /orgs/2/sites/site004
g, customerUser2024, customer001, /orgs/2/sites/site005
g, customerUser2025, customer001, /orgs/2/sites/site001
g, customerUser2025, customer001, /orgs/2/sites/site002
g, customerUser2025, customer001, /orgs/2/sites/site003
g, customerUser2025, customer001, /orgs/2/sites/site004
g, customerUser2025, customer001, /orgs/2/sites/site005
g, customerUser2026, customer001, /orgs/2/sites/site001
g, customerUser2026, customer001, /orgs/2/sites/site002
g, customerUser2026, customer001, /orgs/2/sites/site003
g, customerUser2026, customer001, /orgs/2/sites/site004
g, customerUser2026, customer001, /orgs/2/sites/site005
g, customerUser2027, customer001, /orgs/2/sites/site001
g, customerUser2027, customer001, /orgs/2/sites/site002
g, customerUser2027, customer001, /orgs/2/sites/site003
g, customerUser2027, customer001, /orgs/2/sites/site004
g, customerUser2027, customer001, /orgs/2/sites/site005
g, customerUser2028, customer001, /orgs/2/sites/site001
g, customerUser2028, customer001, /orgs/2/sites/site002
g, customerUser2028, customer001, /orgs/2/sites/site003
g, customerUser2028, customer001, /orgs/2/sites/site004
g, customerUser2028, customer001, /orgs/2/sites/site005
g, customerUser2029, customer001, /orgs/2/sites/site001
g, customerUser2029, customer001, /orgs/2/sites/site002
g, customerUser2029, customer001, /orgs/2/sites/site003
g, customerUser2029, customer001, /orgs/2/sites/site004
g, customerUser2029, customer001, /orgs/2/sites/site005
g, customerUser2030, customer001, /orgs/2/sites/site001
g, customerUser2030, customer001, /orgs/2/sites/site002
g, customerUser2030, customer001, /orgs/2/sites/site003
g, customerUser2030, customer001, /orgs/2/sites/site004
g, customerUser2030, customer001, /orgs/2/sites/site005
g, customerUser2031, customer001, /orgs/2/sites/site001
g, customerUser2031, customer001, /orgs/2/sites/site002
g, customerUser2031, customer001, /orgs/2/sites/site003
g, customerUser2031, customer001, /orgs/2/sites/site004
g, customerUser2031, customer001, /orgs/2/sites/site005
g, customerUser2032, customer001, /orgs/2/sites/site001
g, customerUser2032, customer001, /orgs/2/sites/site002
g, customerUser2032, customer001, /orgs/2/sites/site003
g, customerUser2032, customer001, /orgs/2/sites/site004
g, customerUser2032, customer001, /orgs/2/sites/site005
g, customerUser2033, customer001, /orgs/2/sites/site001
g, customerUser2033, customer001, /orgs/2/sites/site002
g, customerUser2033, customer001, /orgs/2/sites/site003
g, customerUser2033, customer001, /orgs/2/sites/site004
g, customerUser2033, customer001, /orgs/2/sites/site005
g, customerUser2034, customer001, /orgs/2/sites/site001
g, customerUser2034, customer001, /orgs/2/sites/site002
g, customerUser2034, customer001, /orgs/2/sites/site003
g, customerUser2034, customer001, /orgs/2/sites/site004
g, customerUser2034, customer001, /orgs/2/sites/site005
g, customerUser2035, customer001, /orgs/2/sites/site001
g, customerUser2035, customer001, /orgs/2/sites/site002
g, customerUser2035, customer001, /orgs/2/sites/site003
g, customerUser2035, customer001, /orgs/2/sites/site004
g, customerUser2035, customer001, /orgs/2/sites/site005
g, customerUser2036, customer001, /orgs/2/sites/site001
g, customerUser2036, customer001, /orgs/2/sites/site002
g, customerUser2036, customer001, /orgs/2/sites/site003
g, customerUser2036, customer001, /orgs/2/sites/site004
g, customerUser2036, customer001, /orgs/2/sites/site005
g, customerUser2037, customer001, /orgs/2/sites/site001
g, customerUser2037, customer001, /orgs/2/sites/site002
g, customerUser2037, customer001, /orgs/2/sites/site003
g, customerUser2037, customer001, /orgs/2/sites/site004
g, customerUser2037, customer001, /orgs/2/sites/site005
g, customerUser2038, customer001, /orgs/2/sites/site001
g, customerUser2038, customer001, /orgs/2/sites/site002
g, customerUser2038, customer001, /orgs/2/sites/site003
g, customerUser2038, customer001, /orgs/2/sites/site004
g, customerUser2038, customer001, /orgs/2/sites/site005
g, customerUser2039, customer001, /orgs/2/sites/site001
g, customerUser2039, customer001, /orgs/2/sites/site002
g, customerUser2039, customer001, /orgs/2/sites/site003
g, customerUser2039, customer001, /orgs/2/sites/site004
g, customerUser2039, customer001, /orgs/2/sites/site005
g, customerUser2040, customer001, /orgs/2/sites/site001
g, customerUser2040, customer001, /orgs/2/sites/site002
g, customerUser2040, customer001, /orgs/2/sites/site003
g, customerUser2040, customer001, /orgs/2/sites/site004
g, customerUser2040, customer001, /orgs/2/sites/site005
g, customerUser2041, customer001, /orgs/2/sites/site001
g, customerUser2041, customer001, /orgs/2/sites/site002
g, customerUser2041, customer001, /orgs/2/sites/site003
g, customerUser2041, customer001, /orgs/2/sites/site004
g, customerUser2041, customer001, /orgs/2/sites/site005
g, customerUser2042, customer001, /orgs/2/sites/site001
g, customerUser2042, customer001, /orgs/2/sites/site002
g, customerUser2042, customer001, /orgs/2/sites/site003
g, customerUser2042, customer001, /orgs/2/sites/site004
g, customerUser2042, customer001, /orgs/2/sites/site005
g, customerUser2043, customer001, /orgs/2/sites/site001
g, customerUser2043, customer001, /orgs/2/sites/site002
g, customerUser2043, customer001, /orgs/2/sites/site003
g, customerUser2043, customer001, /orgs/2/sites/site004
g, customerUser2043, customer001, /orgs/2/sites/site005
g, customerUser2044, customer001, /orgs/2/sites/site001
g, customerUser2044, customer001, /orgs/2/sites/site002
g, customerUser2044, customer001, /orgs/2/sites/site003
g, customerUser2044, customer001, /orgs/2/sites/site004
g, customerUser2044, customer001, /orgs/2/sites/site005
g, customerUser2045, customer001, /orgs/2/sites/site001
g, customerUser2045, customer001, /orgs/2/sites/site002
g, customerUser2045, customer001, /orgs/2/sites/site003
g, customerUser2045, customer001, /orgs/2/sites/site004
g, customerUser2045, customer001, /orgs/2/sites/site005
g, customerUser2046, customer001, /orgs/2/sites/site001
g, customerUser2046, customer001, /orgs/2/sites/site002
g, customerUser2046, customer001, /orgs/2/sites/site003
g, customerUser2046, customer001, /orgs/2/sites/site004
g, customerUser2046, customer001, /orgs/2/sites/site005
g, customerUser2047, customer001, /orgs/2/sites/site001
g, customerUser2047, customer001, /orgs/2/sites/site002
g, customerUser2047, customer001, /orgs/2/sites/site003
g, customerUser2047, customer001, /orgs/2/sites/site004
g, customerUser2047, customer001, /orgs/2/sites/site005
g, customerUser2048, customer001, /orgs/2/sites/site001
g, customerUser2048, customer001, /orgs/2/sites/site002
g, customerUser2048, customer001, /orgs/2/sites/site003
g, customerUser2048, customer001, /orgs/2/sites/site004
g, customerUser2048, customer001, /orgs/2/sites/site005
g, customerUser2049, customer001, /orgs/2/sites/site001
g, customerUser2049, customer001, /orgs/2/sites/site002
g, customerUser2049, customer001, /orgs/2/sites/site003
g, customerUser2049, customer001, /orgs/2/sites/site004
g, customerUser2049, customer001, /orgs/2/sites/site005
g, customerUser2050, customer001, /orgs/2/sites/site001
g, customerUser2050, customer001, /orgs/2/sites/site002
g, customerUser2050, customer001, /orgs/2/sites/site003
g, customerUser2050, customer001, /orgs/2/sites/site004
g, customerUser2050, customer001, /orgs/2/sites/site005
================================================
FILE: examples/priority_indeterminate_policy.csv
================================================
p, alice, data1, read, indeterminate
================================================
FILE: examples/priority_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/priority_model_enforce_context.conf
================================================
[request_definition]
r = sub, obj, act
r2 = sub, obj
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
m2 = g(r2.sub, p.sub)
================================================
FILE: examples/priority_model_explicit.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = priority, sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/priority_model_explicit_customized.conf
================================================
[request_definition]
r = subject, obj, act
[policy_definition]
p = customized_priority, obj, act, eft, subject
[role_definition]
g = _, _
[policy_effect]
e = priority(p.eft) || deny
[matchers]
m = g(r.subject, p.subject) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/priority_policy.csv
================================================
p, alice, data1, read, allow
p, data1_deny_group, data1, read, deny
p, data1_deny_group, data1, write, deny
p, alice, data1, write, allow
g, alice, data1_deny_group
p, data2_allow_group, data2, read, allow
p, bob, data2, read, deny
p, bob, data2, write, deny
g, bob, data2_allow_group
================================================
FILE: examples/priority_policy_enforce_context.csv
================================================
p, alice, data1, read, allow
p, data1_deny_group, data1, read, deny
p, data1_deny_group, data1, write, deny
p, alice, data1, write, allow
g, alice, data1_deny_group
p, data2_allow_group, data2, read, allow
p, bob, data2, read, deny
p, bob, data2, write, deny
g, bob, data2_allow_group
================================================
FILE: examples/priority_policy_explicit.csv
================================================
p, 10, data1_deny_group, data1, read, deny
p, 10, data1_deny_group, data1, write, deny
p, 10, data2_allow_group, data2, read, allow
p, 10, data2_allow_group, data2, write, allow
p, 1, alice, data1, write, allow
p, 1, alice, data1, read, allow
p, 1, bob, data2, read, deny
g, bob, data2_allow_group
g, alice, data1_deny_group
================================================
FILE: examples/priority_policy_explicit_customized.csv
================================================
p, 10, data1, read, deny, data1_deny_group
p, 10, data1, write, deny, data1_deny_group
p, 10, data2, read, allow, data2_allow_group
p, 10, data2, write, allow, data2_allow_group
p, 1, data1, write, allow, alice
p, 1, data1, read, allow, alice
p, 1, data2, read, deny, bob
g, bob, data2_allow_group
g, alice, data1_deny_group
================================================
FILE: examples/rbac_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_model_in_multi_line.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj \
&& r.act == p.act
================================================
FILE: examples/rbac_model_matcher_using_in_op.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')
================================================
FILE: examples/rbac_model_matcher_using_in_op_bracket.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3']
================================================
FILE: examples/rbac_policy.csv
================================================
p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
================================================
FILE: examples/rbac_with_all_pattern_model.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && g(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act
================================================
FILE: examples/rbac_with_all_pattern_policy.csv
================================================
p, alice, domain1, book_group, read
p, alice, domain2, book_group, write
g, /book/:id, book_group, *
================================================
FILE: examples/rbac_with_constraints_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[constraint_definition]
c = sod("finance_requester", "finance_approver")
c2 = sodMax(["payroll_view", "payroll_edit", "payroll_approve"], 1)
c3 = roleMax("superadmin", 2)
c4 = rolePre("db_admin", "security_trained")
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_cycle_policy.csv
================================================
p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
g, data2_admin, super_admin
g, super_admin, alice
================================================
FILE: examples/rbac_with_deny_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_deny_policy.csv
================================================
p, alice, data1, read, allow
p, bob, data2, write, allow
p, data2_admin, data2, read, allow
p, data2_admin, data2, write, allow
p, alice, data2, write, deny
g, alice, data2_admin
================================================
FILE: examples/rbac_with_different_types_of_roles_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _, (_, _)
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, p.dom) && g2(r.obj, p.dom) && regexMatch(r.act, p.act)
================================================
FILE: examples/rbac_with_different_types_of_roles_policy.csv
================================================
p, role:owner, domain1, _, (read|write)
p, role:developer, domain1, _, read
p, role:owner, domain2, _, (read|write)
p, role:developer, domain2, _, read
g, alice, role:owner, domain1, _, _
g, bob, role:developer, domain2, _, 9999-12-30 00:00:00
g, carol, role:owner, domain2, _, 0000-01-02 00:00:00
g2, data1, domain1
g2, data2, domain2
================================================
FILE: examples/rbac_with_domain_pattern_model.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_domain_pattern_policy.csv
================================================
p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
p, admin, *, data3, read
g, alice, admin, *
g, bob, admin, domain2
================================================
FILE: examples/rbac_with_domain_temporal_roles_model.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _, (_, _)
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_domain_temporal_roles_policy.csv
================================================
p, alice, domain1, data1, read
p, alice, domain1, data1, write
p, data2_admin, domain2, data2, read
p, data2_admin, domain2, data2, write
p, data3_admin, domain3, data3, read
p, data3_admin, domain3, data3, write
p, data4_admin, domain4, data4, read
p, data4_admin, domain4, data4, write
p, data5_admin, domain5, data5, read
p, data5_admin, domain5, data5, write
p, data6_admin, domain6, data6, read
p, data6_admin, domain6, data6, write
p, data7_admin, domain7, data7, read
p, data7_admin, domain7, data7, write
p, data8_admin, domain8, data8, read
p, data8_admin, domain8, data8, write
g, alice, data2_admin, domain2, 0000-01-01 00:00:00, 0000-01-02 00:00:00
g, alice, data3_admin, domain3, 0000-01-01 00:00:00, 9999-12-30 00:00:00
g, alice, data4_admin, domain4, _, _
g, alice, data5_admin, domain5, _, 9999-12-30 00:00:00
g, alice, data6_admin, domain6, _, 0000-01-02 00:00:00
g, alice, data7_admin, domain7, 0000-01-01 00:00:00, _
g, alice, data8_admin, domain8, 9999-12-30 00:00:00, _
================================================
FILE: examples/rbac_with_domains_conditional_model.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _, (_, _)
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && \
(keyMatch(r.act, p.act) || keyMatch2(r.act, p.act) || keyMatch3(r.act, p.act) || keyMatch4(r.act, p.act) || keyMatch5(r.act, p.act) || globMatch(r.act, p.act))
================================================
FILE: examples/rbac_with_domains_conditional_policy.csv
================================================
p, test1, domain1, service1, /list
p, test1, domain1, service1, /get/:id/*
p, test1, domain1, service1, /add
p, test1, domain1, service1, /user/*
p, admin, domain1, service1, /*
p, qa1, domain2, service2, /broadcast
p, qa1, domain2, service2, /trip
p, qa1, domain2, service2, /notify
p, qa1, domain2, service2, /dynamic-sql
g, alice, test1, domain1, _, 2999-12-30 00:00:00
g, bob, qa1, domain2, _, 2999-12-30 00:00:00
g, jack, test1, domain1, _, 0000-12-30 00:00:00
================================================
FILE: examples/rbac_with_domains_model.conf
================================================
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_domains_policy.csv
================================================
p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
g, alice, admin, domain1
g, bob, admin, domain2
================================================
FILE: examples/rbac_with_domains_policy2.csv
================================================
p, admin, domain1, data1, read
p, admin, domain1, data1, write
p, admin, domain2, data2, read
p, admin, domain2, data2, write
p, user, domain3, data2, read
g, alice, admin, domain1
g, alice, admin, domain2
g, bob, admin, domain2
g, bob, user, domain3
================================================
FILE: examples/rbac_with_hierarchy_policy.csv
================================================
p, alice, data1, read
p, bob, data2, write
p, data1_admin, data1, read
p, data1_admin, data1, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, admin
g, admin, data1_admin
g, admin, data2_admin
================================================
FILE: examples/rbac_with_hierarchy_with_domains_policy.csv
================================================
p, role:reader, domain1, data1, read
p, role:writer, domain1, data1, write
p, alice, domain1, data2, read
p, alice, domain2, data2, read
g, role:global_admin, role:reader, domain1
g, role:global_admin, role:writer, domain1
g, alice, role:global_admin, domain1
================================================
FILE: examples/rbac_with_multiple_policy_model.conf
================================================
[request_definition]
r = user, thing, action
[policy_definition]
p = role, thing, action
p2 = role, action
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.user, p.role) && r.thing == p.thing && r.action == p.action
m2 = g(r.user, p2.role) && r.action == p.action
[role_definition]
g = _,_
g2 = _,_
================================================
FILE: examples/rbac_with_multiple_policy_policy.csv
================================================
p, user, /data, GET
p, admin, /data, POST
p2, user, view
p2, admin, create
g, admin, user
g, alice, admin
g2, alice, user
================================================
FILE: examples/rbac_with_not_deny_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_pattern_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)
================================================
FILE: examples/rbac_with_pattern_policy.csv
================================================
p, alice, /pen/1, GET
p, alice, /pen2/1, GET
p, book_admin, book_group, GET
p, pen_admin, pen_group, GET
p, *, pen3_group, GET
p, /book/admin/:id, pen4_group, GET
g, /book/user/:id, /book/admin/1
p, /book/leader/2, pen4_group, POST
g, /book/user/:id, /book/leader/2
g, alice, book_admin
g, bob, pen_admin
g, cathy, /book/1/2/3/4/5
g, cathy, pen_admin
g2, /book/*, book_group
g2, /book/:id, book_group
g2, /pen/:id, pen_group
g2, /book2/{id}, book_group
g2, /pen2/{id}, pen_group
g2, /pen3/:id, pen3_group
g2, /pen4/:id, pen4_group
================================================
FILE: examples/rbac_with_resource_roles_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
================================================
FILE: examples/rbac_with_resource_roles_policy.csv
================================================
p, alice, data1, read
p, bob, data2, write
p, data_group_admin, data_group, write
g, alice, data_group_admin
g2, data1, data_group
g2, data2, data_group
================================================
FILE: examples/rbac_with_temporal_roles_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _, (_, _)
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/rbac_with_temporal_roles_policy.csv
================================================
p, alice, data1, read
p, alice, data1, write
p, data2_admin, data2, read
p, data2_admin, data2, write
p, data3_admin, data3, read
p, data3_admin, data3, write
p, data4_admin, data4, read
p, data4_admin, data4, write
p, data5_admin, data5, read
p, data5_admin, data5, write
p, data6_admin, data6, read
p, data6_admin, data6, write
p, data7_admin, data7, read
p, data7_admin, data7, write
p, data8_admin, data8, read
p, data8_admin, data8, write
g, alice, data2_admin, 0000-01-01 00:00:00, 0000-01-02 00:00:00
g, alice, data3_admin, 0000-01-01 00:00:00, 9999-12-30 00:00:00
g, alice, data4_admin, _, _
g, alice, data5_admin, _, 9999-12-30 00:00:00
g, alice, data6_admin, _, 0000-01-02 00:00:00
g, alice, data7_admin, 0000-01-01 00:00:00, _
g, alice, data8_admin, 9999-12-30 00:00:00, _
================================================
FILE: examples/rebac_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = role, obj_type, act
[role_definition]
g = _, _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, r.obj, p.role) && g2(r.obj, p.obj_type) && r.act == p.act
================================================
FILE: examples/rebac_policy.csv
================================================
p, collaborator, doc, read
g, alice, doc1, collaborator
g, bob, doc2, collaborator
g2, doc1, doc
g2, doc2, doc
================================================
FILE: examples/subject_priority_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = subjectPriority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/subject_priority_model_with_domain.conf
================================================
[request_definition]
r = sub, obj, dom, act
[policy_definition]
p = sub, obj, dom, act, eft #sub can't change position,must be first
[role_definition]
g = _, _, _
[policy_effect]
e = subjectPriority(p.eft) || deny
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
================================================
FILE: examples/subject_priority_policy.csv
================================================
p, root, data1, read, deny
p, admin, data1, read, deny
p, editor, data1, read, deny
p, subscriber, data1, read, deny
p, jane, data1, read, allow
p, alice, data1, read, allow
g, admin, root
g, editor, admin
g, subscriber, admin
g, jane, editor
g, alice, subscriber
================================================
FILE: examples/subject_priority_policy_with_domain.csv
================================================
p, admin, data1, domain1, write, deny
p, alice, data1, domain1, write, allow
p, admin, data2, domain2, write, deny
p, bob, data2, domain2, write, allow
g, alice, admin, domain1
g, bob, admin, domain2
================================================
FILE: examples/syntax_matcher_model.conf
================================================
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p_eft == allow))
[matchers]
m = r.sub == "a.p.p.l.e"
================================================
FILE: filter_test.go
================================================
// Copyright 2018 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
"github.com/casbin/casbin/v3/util"
)
func TestInitFilteredAdapter(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
// policy should not be loaded yet
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, false)
}
func TestLoadFilteredPolicy(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadPolicy(); err != nil {
t.Errorf("unexpected error in LoadPolicy: %v", err)
}
// validate initial conditions
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, true)
testHasPolicy(t, e, []string{"admin", "domain2", "data2", "read"}, true)
if err := e.LoadFilteredPolicy(&fileadapter.Filter{
P: []string{"", "domain1"},
G: []string{"", "", "domain1"},
}); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
if !e.IsFiltered() {
t.Errorf("adapter did not set the filtered flag correctly")
}
// only policies for domain1 should be loaded
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, true)
testHasPolicy(t, e, []string{"admin", "domain2", "data2", "read"}, false)
if err := e.SavePolicy(); err == nil {
t.Errorf("enforcer did not prevent saving filtered policy")
}
if err := e.GetAdapter().SavePolicy(e.GetModel()); err == nil {
t.Errorf("adapter did not prevent saving filtered policy")
}
}
func TestLoadMoreTypeFilteredPolicy(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_pattern_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_pattern_model.conf", adapter)
if err := e.LoadPolicy(); err != nil {
t.Errorf("unexpected error in LoadPolicy: %v", err)
}
e.AddNamedMatchingFunc("g2", "matching func", util.KeyMatch2)
_ = e.BuildRoleLinks()
testEnforce(t, e, "alice", "/book/1", "GET", true)
// validate initial conditions
testHasPolicy(t, e, []string{"book_admin", "book_group", "GET"}, true)
testHasPolicy(t, e, []string{"pen_admin", "pen_group", "GET"}, true)
if err := e.LoadFilteredPolicy(&fileadapter.Filter{
P: []string{"book_admin"},
G: []string{"alice"},
G2: []string{"", "book_group"},
}); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
if !e.IsFiltered() {
t.Errorf("adapter did not set the filtered flag correctly")
}
testHasPolicy(t, e, []string{"alice", "/pen/1", "GET"}, false)
testHasPolicy(t, e, []string{"alice", "/pen2/1", "GET"}, false)
testHasPolicy(t, e, []string{"pen_admin", "pen_group", "GET"}, false)
testHasGroupingPolicy(t, e, []string{"alice", "book_admin"}, true)
testHasGroupingPolicy(t, e, []string{"bob", "pen_admin"}, false)
testHasGroupingPolicy(t, e, []string{"cathy", "pen_admin"}, false)
testHasGroupingPolicy(t, e, []string{"cathy", "/book/1/2/3/4/5"}, false)
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "alice", "/pen/1", "GET", false)
}
func TestAppendFilteredPolicy(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadPolicy(); err != nil {
t.Errorf("unexpected error in LoadPolicy: %v", err)
}
// validate initial conditions
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, true)
testHasPolicy(t, e, []string{"admin", "domain2", "data2", "read"}, true)
if err := e.LoadFilteredPolicy(&fileadapter.Filter{
P: []string{"", "domain1"},
G: []string{"", "", "domain1"},
}); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
if !e.IsFiltered() {
t.Errorf("adapter did not set the filtered flag correctly")
}
// only policies for domain1 should be loaded
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, true)
testHasPolicy(t, e, []string{"admin", "domain2", "data2", "read"}, false)
// disable clear policy and load second domain
if err := e.LoadIncrementalFilteredPolicy(&fileadapter.Filter{
P: []string{"", "domain2"},
G: []string{"", "", "domain2"},
}); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
// both domain policies should be loaded
testHasPolicy(t, e, []string{"admin", "domain1", "data1", "read"}, true)
testHasPolicy(t, e, []string{"admin", "domain2", "data2", "read"}, true)
}
func TestFilteredPolicyInvalidFilter(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadFilteredPolicy([]string{"", "domain1"}); err == nil {
t.Errorf("expected error in LoadFilteredPolicy, but got nil")
}
}
func TestFilteredPolicyEmptyFilter(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadFilteredPolicy(nil); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
if e.IsFiltered() {
t.Errorf("adapter did not reset the filtered flag correctly")
}
if err := e.SavePolicy(); err != nil {
t.Errorf("unexpected error in SavePolicy: %v", err)
}
}
func TestUnsupportedFilteredPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
err := e.LoadFilteredPolicy(&fileadapter.Filter{
P: []string{"", "domain1"},
G: []string{"", "", "domain1"},
})
if err == nil {
t.Errorf("encorcer should have reported incompatibility error")
}
}
func TestFilteredAdapterEmptyFilepath(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadFilteredPolicy(nil); err != nil {
t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
}
}
func TestFilteredAdapterInvalidFilepath(t *testing.T) {
e, _ := NewEnforcer()
adapter := fileadapter.NewFilteredAdapter("examples/does_not_exist_policy.csv")
_ = e.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)
if err := e.LoadFilteredPolicy(nil); err == nil {
t.Errorf("expected error in LoadFilteredPolicy, but got nil")
}
}
================================================
FILE: frontend.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"bytes"
"encoding/json"
)
func CasbinJsGetPermissionForUser(e IEnforcer, user string) (string, error) {
model := e.GetModel()
m := map[string]interface{}{}
m["m"] = model.ToText()
pRules := [][]string{}
for ptype := range model["p"] {
policies, err := model.GetPolicy("p", ptype)
if err != nil {
return "", err
}
for _, rules := range policies {
pRules = append(pRules, append([]string{ptype}, rules...))
}
}
m["p"] = pRules
gRules := [][]string{}
for ptype := range model["g"] {
policies, err := model.GetPolicy("g", ptype)
if err != nil {
return "", err
}
for _, rules := range policies {
gRules = append(gRules, append([]string{ptype}, rules...))
}
}
m["g"] = gRules
result := bytes.NewBuffer([]byte{})
encoder := json.NewEncoder(result)
encoder.SetEscapeHTML(false)
err := encoder.Encode(m)
return result.String(), err
}
================================================
FILE: frontend_old.go
================================================
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import "encoding/json"
func CasbinJsGetPermissionForUserOld(e IEnforcer, user string) ([]byte, error) {
policy, err := e.GetImplicitPermissionsForUser(user)
if err != nil {
return nil, err
}
permission := make(map[string][]string)
for i := 0; i < len(policy); i++ {
permission[policy[i][2]] = append(permission[policy[i][2]], policy[i][1])
}
b, _ := json.Marshal(permission)
return b, nil
}
================================================
FILE: frontend_old_test.go
================================================
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"encoding/json"
"testing"
)
func contains(arr []string, target string) bool {
for _, item := range arr {
if item == target {
return true
}
}
return false
}
func TestCasbinJsGetPermissionForUserOld(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
panic(err)
}
targetStr, _ := CasbinJsGetPermissionForUserOld(e, "alice")
t.Log("GetPermissionForUser Alice", string(targetStr))
aliceTarget := make(map[string][]string)
err = json.Unmarshal(targetStr, &aliceTarget)
if err != nil {
t.Errorf("Test error: %s", err)
}
perm, ok := aliceTarget["read"]
if !ok {
t.Errorf("Test error: Alice doesn't have read permission")
}
if !contains(perm, "data1") {
t.Errorf("Test error: Alice cannot read data1")
}
if !contains(perm, "data2") {
t.Errorf("Test error: Alice cannot read data2")
}
perm, ok = aliceTarget["write"]
if !ok {
t.Errorf("Test error: Alice doesn't have write permission")
}
if contains(perm, "data1") {
t.Errorf("Test error: Alice can write data1")
}
if !contains(perm, "data2") {
t.Errorf("Test error: Alice cannot write data2")
}
targetStr, _ = CasbinJsGetPermissionForUserOld(e, "bob")
t.Log("GetPermissionForUser Bob", string(targetStr))
bobTarget := make(map[string][]string)
err = json.Unmarshal(targetStr, &bobTarget)
if err != nil {
t.Errorf("Test error: %s", err)
}
_, ok = bobTarget["read"]
if ok {
t.Errorf("Test error: Bob has read permission")
}
perm, ok = bobTarget["write"]
if !ok {
t.Errorf("Test error: Bob doesn't have permission")
}
if !contains(perm, "data2") {
t.Errorf("Test error: Bob cannot write data2")
}
if contains(perm, "data1") {
t.Errorf("Test error: Bob can write data1")
}
if contains(perm, "data_not_exist") {
t.Errorf("Test error: Bob can access a non-existing data")
}
_, ok = bobTarget["rm_rf"]
if ok {
t.Errorf("Someone can have a non-existing action (rm -rf)")
}
}
================================================
FILE: frontend_test.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"encoding/json"
"io/ioutil"
"regexp"
"strings"
"testing"
)
func TestCasbinJsGetPermissionForUser(t *testing.T) {
e, err := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv")
if err != nil {
panic(err)
}
receivedString, err := CasbinJsGetPermissionForUser(e, "alice") // make sure CasbinJsGetPermissionForUser can be used with a SyncedEnforcer.
if err != nil {
t.Errorf("Test error: %s", err)
}
received := map[string]interface{}{}
err = json.Unmarshal([]byte(receivedString), &received)
if err != nil {
t.Errorf("Test error: %s", err)
}
expectedModel, err := ioutil.ReadFile("examples/rbac_model.conf")
if err != nil {
t.Errorf("Test error: %s", err)
}
// Normalize line endings to \n for cross-platform compatibility
expectedModelStr := regexp.MustCompile("(\r?\n)+").ReplaceAllString(string(expectedModel), "\n")
actualModelStr := strings.TrimSpace(received["m"].(string))
expectedModelStr = strings.TrimSpace(expectedModelStr)
if actualModelStr != expectedModelStr {
t.Errorf("%s supposed to be %s", actualModelStr, expectedModelStr)
}
expectedPolicies, err := ioutil.ReadFile("examples/rbac_with_hierarchy_policy.csv")
if err != nil {
t.Errorf("Test error: %s", err)
}
expectedPoliciesItem := regexp.MustCompile(",|\n").Split(string(expectedPolicies), -1)
i := 0
for _, sArr := range received["p"].([]interface{}) {
for _, s := range sArr.([]interface{}) {
if strings.TrimSpace(s.(string)) != strings.TrimSpace(expectedPoliciesItem[i]) {
t.Errorf("%s supposed to be %s", strings.TrimSpace(s.(string)), strings.TrimSpace(expectedPoliciesItem[i]))
}
i++
}
}
}
================================================
FILE: go.mod
================================================
module github.com/casbin/casbin/v3
require (
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/casbin/govaluate v1.3.0
github.com/google/uuid v1.6.0
)
go 1.13
================================================
FILE: go.sum
================================================
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
================================================
FILE: internal_api.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
Err "github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/log"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
const (
notImplemented = "not implemented"
)
func (e *Enforcer) shouldPersist() bool {
return e.adapter != nil && e.autoSave
}
func (e *Enforcer) shouldNotify() bool {
return e.watcher != nil && e.autoNotifyWatcher
}
// validateConstraintsForGroupingPolicy validates constraints for grouping policy changes.
// It returns an error if constraint validation fails.
func (e *Enforcer) validateConstraintsForGroupingPolicy() error {
return e.model.ValidateConstraints()
}
// addPolicy adds a rule to the current policy.
func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})
}
hasPolicy, err := e.model.HasPolicy(sec, ptype, rule)
if hasPolicy || err != nil {
return false, err
}
if e.shouldPersist() {
if err = e.adapter.AddPolicy(sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
err = e.model.AddPolicy(sec, ptype, rule)
if err != nil {
return false, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})
if err != nil {
return true, err
}
// Validate constraints after adding grouping policy
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return true, nil
}
// addPoliciesWithoutNotify adds rules to the current policy without notify
// If autoRemoveRepeat == true, existing rules are automatically filtered
// Otherwise, false is returned directly.
func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.AddPolicies(sec, ptype, rules)
}
if !autoRemoveRepeat {
hasPolicies, err := e.model.HasPolicies(sec, ptype, rules)
if hasPolicies || err != nil {
return false, err
}
}
if e.shouldPersist() {
if err := e.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
err := e.model.AddPolicies(sec, ptype, rules)
if err != nil {
return false, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)
if err != nil {
return true, err
}
err = e.BuildIncrementalConditionalRoleLinks(model.PolicyAdd, ptype, rules)
if err != nil {
return true, err
}
// Validate constraints after adding grouping policies
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return true, nil
}
// removePolicy removes a rule from the current policy.
func (e *Enforcer) removePolicyWithoutNotify(sec string, ptype string, rule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype, [][]string{rule})
}
if e.shouldPersist() {
if err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleRemoved, err := e.model.RemovePolicy(sec, ptype, rule)
if !ruleRemoved || err != nil {
return ruleRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{rule})
if err != nil {
return ruleRemoved, err
}
// Validate constraints after removing grouping policy
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return ruleRemoved, nil
}
func (e *Enforcer) updatePolicyWithoutNotify(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule, newRule)
}
if e.shouldPersist() {
if err := e.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleUpdated, err := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule
if err != nil {
return ruleUpdated, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule
if err != nil {
return ruleUpdated, err
}
// Validate constraints after updating grouping policy
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return ruleUpdated, nil
}
func (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
if len(newRules) != len(oldRules) {
return false, fmt.Errorf("the length of oldRules should be equal to the length of newRules, but got the length of oldRules is %d, the length of newRules is %d", len(oldRules), len(newRules))
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules, newRules)
}
if e.shouldPersist() {
if err := e.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleUpdated, err := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)
if !ruleUpdated || err != nil {
return ruleUpdated, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
if err != nil {
return ruleUpdated, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
if err != nil {
return ruleUpdated, err
}
// Validate constraints after updating grouping policies
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return ruleUpdated, nil
}
// removePolicies removes rules from the current policy.
func (e *Enforcer) removePoliciesWithoutNotify(sec string, ptype string, rules [][]string) (bool, error) {
if hasPolicies, err := e.model.HasPolicies(sec, ptype, rules); !hasPolicies || err != nil {
return hasPolicies, err
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemovePolicies(sec, ptype, rules)
}
if e.shouldPersist() {
if err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
rulesRemoved, err := e.model.RemovePolicies(sec, ptype, rules)
if !rulesRemoved || err != nil {
return rulesRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, rules)
if err != nil {
return rulesRemoved, err
}
// Validate constraints after removing grouping policies
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return rulesRemoved, nil
}
// removeFilteredPolicy removes rules based on field filters from the current policy.
func (e *Enforcer) removeFilteredPolicyWithoutNotify(sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {
if len(fieldValues) == 0 {
return false, Err.ErrInvalidFieldValuesParameter
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return true, e.dispatcher.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
}
if e.shouldPersist() {
if err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return false, err
}
}
}
ruleRemoved, effects, err := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
if !ruleRemoved || err != nil {
return ruleRemoved, err
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, effects)
if err != nil {
return ruleRemoved, err
}
// Validate constraints after removing filtered grouping policies
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return false, err
}
}
return ruleRemoved, nil
}
func (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
var (
oldRules [][]string
err error
)
if _, err = e.model.GetAssertion(sec, ptype); err != nil {
return oldRules, err
}
if e.shouldPersist() {
if oldRules, err = e.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {
if err.Error() != notImplemented {
return nil, err
}
}
// For compatibility, because some adapters return oldRules containing ptype, see https://github.com/casbin/xorm-adapter/issues/49
for i, oldRule := range oldRules {
if len(oldRules[i]) == len(e.model[sec][ptype].Tokens)+1 {
oldRules[i] = oldRule[1:]
}
}
}
if e.dispatcher != nil && e.autoNotifyDispatcher {
return oldRules, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)
}
ruleChanged, err := e.model.RemovePolicies(sec, ptype, oldRules)
if err != nil {
return oldRules, err
}
err = e.model.AddPolicies(sec, ptype, newRules)
if err != nil {
return oldRules, err
}
ruleChanged = ruleChanged && len(newRules) != 0
if !ruleChanged {
return make([][]string, 0), nil
}
if sec == "g" {
err := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules
if err != nil {
return oldRules, err
}
err = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules
if err != nil {
return oldRules, err
}
// Validate constraints after updating filtered grouping policies
if err := e.validateConstraintsForGroupingPolicy(); err != nil {
return oldRules, err
}
}
return oldRules, nil
}
// addPolicy adds a rule to the current policy.
func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, error) {
ok, err := e.logPolicyOperation(log.EventAddPolicy, sec, rule, func() (bool, error) {
return e.addPolicyWithoutNotify(sec, ptype, rule)
})
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var notifyErr error
if watcher, isWatcherEx := e.watcher.(persist.WatcherEx); isWatcherEx {
notifyErr = watcher.UpdateForAddPolicy(sec, ptype, rule...)
} else {
notifyErr = e.watcher.Update()
}
return true, notifyErr
}
return true, nil
}
// addPolicies adds rules to the current policy.
// If autoRemoveRepeat == true, existing rules are automatically filtered
// Otherwise, false is returned directly.
func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
ok, err := e.addPoliciesWithoutNotify(sec, ptype, rules, autoRemoveRepeat)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForAddPolicies(sec, ptype, rules...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
// removePolicy removes a rule from the current policy.
func (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool, error) {
ok, err := e.logPolicyOperation(log.EventRemovePolicy, sec, rule, func() (bool, error) {
return e.removePolicyWithoutNotify(sec, ptype, rule)
})
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var notifyErr error
if watcher, isWatcherEx := e.watcher.(persist.WatcherEx); isWatcherEx {
notifyErr = watcher.UpdateForRemovePolicy(sec, ptype, rule...)
} else {
notifyErr = e.watcher.Update()
}
return true, notifyErr
}
return true, nil
}
func (e *Enforcer) updatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
ok, err := e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicy(sec, ptype, oldRule, newRule)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *Enforcer) updatePolicies(sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
ok, err := e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
// removePolicies removes rules from the current policy.
func (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (bool, error) {
ok, err := e.removePoliciesWithoutNotify(sec, ptype, rules)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForRemovePolicies(sec, ptype, rules...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
// removeFilteredPolicy removes rules based on field filters from the current policy.
func (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {
ok, err := e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex, fieldValues)
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.WatcherEx); ok {
err = watcher.UpdateForRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *Enforcer) updateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
oldRules, err := e.updateFilteredPoliciesWithoutNotify(sec, ptype, newRules, fieldIndex, fieldValues...)
ok := len(oldRules) != 0
if !ok || err != nil {
return ok, err
}
if e.shouldNotify() {
var err error
if watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {
err = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)
} else {
err = e.watcher.Update()
}
return true, err
}
return true, nil
}
func (e *Enforcer) GetFieldIndex(ptype string, field string) (int, error) {
return e.model.GetFieldIndex(ptype, field)
}
func (e *Enforcer) SetFieldIndex(ptype string, field string, index int) {
assertion := e.model["p"][ptype]
assertion.FieldIndexMutex.Lock()
assertion.FieldIndexMap[field] = index
assertion.FieldIndexMutex.Unlock()
}
================================================
FILE: lbac_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
func testEnforceLBAC(t *testing.T, e *Enforcer, sub string, subConf, subInteg float64, obj string, objConf, objInteg float64, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, subConf, subInteg, obj, objConf, objInteg, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, conf=%v, integ=%v, %s, conf=%v, integ=%v, %s: %t, supposed to be %t", sub, subConf, subInteg, obj, objConf, objInteg, act, myRes, res)
}
}
func TestLBACModel(t *testing.T) {
e, _ := NewEnforcer("examples/lbac_model.conf")
t.Log("Testing normal read operation scenarios")
testEnforceLBAC(t, e, "admin", 5, 5, "file_topsecret", 3, 3, "read", true) // both high
testEnforceLBAC(t, e, "manager", 4, 4, "file_secret", 4, 2, "read", true) // confidentiality equal, integrity higher
testEnforceLBAC(t, e, "staff", 3, 3, "file_internal", 2, 3, "read", true) // confidentiality higher, integrity equal
testEnforceLBAC(t, e, "guest", 2, 2, "file_public", 2, 2, "read", true) // both dimensions equal
t.Log("Testing read operation violation scenarios")
testEnforceLBAC(t, e, "staff", 3, 3, "file_secret", 4, 2, "read", false) // insufficient confidentiality level
testEnforceLBAC(t, e, "manager", 4, 4, "file_sensitive", 3, 5, "read", false) // insufficient integrity level
testEnforceLBAC(t, e, "guest", 2, 2, "file_internal", 3, 1, "read", false) // insufficient confidentiality level
testEnforceLBAC(t, e, "staff", 3, 3, "file_protected", 1, 4, "read", false) // insufficient integrity level
t.Log("Testing normal write operation scenarios")
testEnforceLBAC(t, e, "guest", 2, 2, "file_public", 2, 2, "write", true) // both dimensions equal
testEnforceLBAC(t, e, "staff", 3, 3, "file_internal", 5, 4, "write", true) // both low
testEnforceLBAC(t, e, "manager", 4, 4, "file_secret", 4, 5, "write", true) // confidentiality equal, integrity low
testEnforceLBAC(t, e, "admin", 5, 5, "file_archive", 5, 5, "write", true) // both dimensions equal
t.Log("Testing write operation violation scenarios")
testEnforceLBAC(t, e, "manager", 4, 4, "file_internal", 3, 5, "write", false) // confidentiality level too high
testEnforceLBAC(t, e, "staff", 3, 3, "file_public", 2, 2, "write", false) // both dimensions too high
testEnforceLBAC(t, e, "admin", 5, 5, "file_secret", 5, 4, "write", false) // integrity level too high
testEnforceLBAC(t, e, "guest", 2, 2, "file_private", 1, 3, "write", false) // confidentiality level too high
}
================================================
FILE: log/default_logger.go
================================================
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"fmt"
"io"
"os"
"strings"
"time"
)
// DefaultLogger is the default implementation of the Logger interface.
type DefaultLogger struct {
output io.Writer
eventTypes map[EventType]bool
logCallback func(entry *LogEntry) error
}
// NewDefaultLogger creates a new DefaultLogger instance.
// If no output is set via SetOutput, it defaults to os.Stdout.
func NewDefaultLogger() *DefaultLogger {
return &DefaultLogger{
output: os.Stdout,
eventTypes: make(map[EventType]bool),
}
}
// SetOutput sets the output destination for the logger.
// It can be set to a buffer or any io.Writer.
func (l *DefaultLogger) SetOutput(w io.Writer) {
if w != nil {
l.output = w
}
}
// SetEventTypes sets the event types that should be logged.
// Only events matching these types will have IsActive set to true.
func (l *DefaultLogger) SetEventTypes(eventTypes []EventType) error {
l.eventTypes = make(map[EventType]bool)
for _, et := range eventTypes {
l.eventTypes[et] = true
}
return nil
}
// OnBeforeEvent is called before an event occurs.
// It sets the StartTime and determines if the event should be active based on configured event types.
func (l *DefaultLogger) OnBeforeEvent(entry *LogEntry) error {
if entry == nil {
return fmt.Errorf("log entry is nil")
}
entry.StartTime = time.Now()
// Set IsActive based on whether this event type is enabled
// If no event types are configured, all events are considered active
if len(l.eventTypes) == 0 {
entry.IsActive = true
} else {
entry.IsActive = l.eventTypes[entry.EventType]
}
return nil
}
// OnAfterEvent is called after an event completes.
// It calculates the duration, logs the entry if active, and calls the user callback if set.
func (l *DefaultLogger) OnAfterEvent(entry *LogEntry) error {
if entry == nil {
return fmt.Errorf("log entry is nil")
}
entry.EndTime = time.Now()
entry.Duration = entry.EndTime.Sub(entry.StartTime)
// Only log if the event is active
if entry.IsActive && l.output != nil {
if err := l.writeLog(entry); err != nil {
return err
}
}
// Call user-provided callback if set
if l.logCallback != nil {
if err := l.logCallback(entry); err != nil {
return err
}
}
return nil
}
// SetLogCallback sets a user-provided callback function.
// The callback is called at the end of OnAfterEvent.
func (l *DefaultLogger) SetLogCallback(callback func(entry *LogEntry) error) error {
l.logCallback = callback
return nil
}
// writeLog writes the log entry to the configured output.
func (l *DefaultLogger) writeLog(entry *LogEntry) error {
var logMessage string
switch entry.EventType {
case EventEnforce:
logMessage = fmt.Sprintf("[%s] Enforce: subject=%s, object=%s, action=%s, domain=%s, allowed=%v, duration=%v\n",
entry.EventType, entry.Subject, entry.Object, entry.Action, entry.Domain, entry.Allowed, entry.Duration)
case EventAddPolicy, EventRemovePolicy:
logMessage = fmt.Sprintf("[%s] RuleCount=%d, duration=%v\n",
entry.EventType, entry.RuleCount, entry.Duration)
case EventLoadPolicy, EventSavePolicy:
logMessage = fmt.Sprintf("[%s] RuleCount=%d, duration=%v\n",
entry.EventType, entry.RuleCount, entry.Duration)
default:
logMessage = fmt.Sprintf("[%s] duration=%v\n",
entry.EventType, entry.Duration)
}
if entry.Error != nil {
logMessage = strings.TrimSuffix(logMessage, "\n")
logMessage = fmt.Sprintf("%s Error: %v\n", logMessage, entry.Error)
}
_, err := l.output.Write([]byte(logMessage))
return err
}
================================================
FILE: log/logger.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
// Logger defines the interface for event-driven logging in Casbin.
type Logger interface {
SetEventTypes([]EventType) error
// OnBeforeEvent is called before an event occurs and returns a handle for context.
OnBeforeEvent(entry *LogEntry) error
// OnAfterEvent is called after an event completes with the handle and final entry.
OnAfterEvent(entry *LogEntry) error
SetLogCallback(func(entry *LogEntry) error) error
}
================================================
FILE: log/types.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import "time"
// EventType represents the type of logging event.
type EventType string
// Event type constants.
const (
EventEnforce EventType = "enforce"
EventAddPolicy EventType = "addPolicy"
EventRemovePolicy EventType = "removePolicy"
EventLoadPolicy EventType = "loadPolicy"
EventSavePolicy EventType = "savePolicy"
)
// LogEntry represents a complete log entry for a Casbin event.
type LogEntry struct {
IsActive bool
// EventType is the type of the event being logged.
EventType EventType
StartTime time.Time
EndTime time.Time
Duration time.Duration
// Enforce parameters.
// Subject is the user or entity requesting access.
Subject string
// Object is the resource being accessed.
Object string
// Action is the operation being performed.
Action string
// Domain is the domain/tenant for multi-tenant scenarios.
Domain string
// Allowed indicates whether the enforcement request was allowed.
Allowed bool
// Rules contains the policy rules involved in the operation.
Rules [][]string
// RuleCount is the number of rules affected by the operation.
RuleCount int
// Error contains any error that occurred during the event.
Error error
}
================================================
FILE: logger_test.go
================================================
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"bytes"
"strings"
"testing"
"github.com/casbin/casbin/v3/log"
)
func verifyBufferOutput(t *testing.T, logOutput string) {
t.Helper()
expectedEvents := []string{"[enforce]", "[addPolicy]", "[removePolicy]", "[savePolicy]", "[loadPolicy]"}
for _, event := range expectedEvents {
if !strings.Contains(logOutput, event) {
t.Errorf("Expected log output to contain %s event", event)
}
}
}
func verifyCallbackEntries(t *testing.T, entries []*log.LogEntry) {
t.Helper()
found := map[log.EventType]bool{}
for _, entry := range entries {
found[entry.EventType] = true
switch entry.EventType {
case log.EventEnforce:
if entry.Subject == "" && entry.Object == "" && entry.Action == "" {
t.Errorf("Expected enforce entry to have subject, object, and action")
}
case log.EventAddPolicy, log.EventRemovePolicy:
if entry.RuleCount != 1 {
t.Errorf("Expected %s entry to have RuleCount=1, got %d", entry.EventType, entry.RuleCount)
}
case log.EventSavePolicy, log.EventLoadPolicy:
if entry.RuleCount == 0 {
t.Errorf("Expected %s entry to have RuleCount>0", entry.EventType)
}
}
}
requiredEvents := []log.EventType{
log.EventEnforce, log.EventAddPolicy, log.EventRemovePolicy,
log.EventSavePolicy, log.EventLoadPolicy,
}
for _, eventType := range requiredEvents {
if !found[eventType] {
t.Errorf("Expected to find %s in callback entries", eventType)
}
}
}
func TestEnforcerWithDefaultLogger(t *testing.T) {
// Create enforcer with RBAC model and policy
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Create a buffer to capture log output
var buf bytes.Buffer
logger := log.NewDefaultLogger()
logger.SetOutput(&buf)
// Set up a callback to track log entries
var callbackEntries []*log.LogEntry
err = logger.SetLogCallback(func(entry *log.LogEntry) error {
// Create a copy of the entry to store
entryCopy := *entry
callbackEntries = append(callbackEntries, &entryCopy)
return nil
})
if err != nil {
t.Fatalf("Failed to set log callback: %v", err)
}
// Set the logger on the enforcer
e.SetLogger(logger)
// Test Enforce events
if result, err := e.Enforce("alice", "data1", "read"); err != nil {
t.Fatalf("Enforce failed: %v", err)
} else if !result {
t.Errorf("Expected alice to have read access to data1")
}
if result, err := e.Enforce("bob", "data2", "write"); err != nil {
t.Fatalf("Enforce failed: %v", err)
} else if !result {
t.Errorf("Expected bob to have write access to data2")
}
// Test AddPolicy event
if added, err := e.AddPolicy("charlie", "data3", "read"); err != nil {
t.Fatalf("AddPolicy failed: %v", err)
} else if !added {
t.Errorf("Expected policy to be added")
}
// Test RemovePolicy event
if removed, err := e.RemovePolicy("charlie", "data3", "read"); err != nil {
t.Fatalf("RemovePolicy failed: %v", err)
} else if !removed {
t.Errorf("Expected policy to be removed")
}
// Test SavePolicy and LoadPolicy events
if err := e.SavePolicy(); err != nil {
t.Fatalf("SavePolicy failed: %v", err)
}
if err := e.LoadPolicy(); err != nil {
t.Fatalf("LoadPolicy failed: %v", err)
}
// Verify buffer output and callback entries
verifyBufferOutput(t, buf.String())
if len(callbackEntries) == 0 {
t.Fatalf("Expected callback to be called, but got no entries")
}
verifyCallbackEntries(t, callbackEntries)
}
func TestSetEventTypes(t *testing.T) {
// Create enforcer with RBAC model and policy
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Create a buffer to capture log output
var buf bytes.Buffer
logger := log.NewDefaultLogger()
logger.SetOutput(&buf)
// Set up a callback to track log entries
var callbackEntries []*log.LogEntry
err = logger.SetLogCallback(func(entry *log.LogEntry) error {
// Create a copy of the entry to store
entryCopy := *entry
callbackEntries = append(callbackEntries, &entryCopy)
return nil
})
if err != nil {
t.Fatalf("Failed to set log callback: %v", err)
}
// Configure logger to only log EventEnforce and EventAddPolicy
err = logger.SetEventTypes([]log.EventType{log.EventEnforce, log.EventAddPolicy})
if err != nil {
t.Fatalf("Failed to set event types: %v", err)
}
// Set the logger on the enforcer
e.SetLogger(logger)
// Perform various operations
_, err = e.Enforce("alice", "data1", "read")
if err != nil {
t.Fatalf("Enforce failed: %v", err)
}
_, err = e.AddPolicy("charlie", "data3", "read")
if err != nil {
t.Fatalf("AddPolicy failed: %v", err)
}
_, err = e.RemovePolicy("charlie", "data3", "read")
if err != nil {
t.Fatalf("RemovePolicy failed: %v", err)
}
if err := e.LoadPolicy(); err != nil {
t.Fatalf("LoadPolicy failed: %v", err)
}
// Verify buffer output only contains EventEnforce and EventAddPolicy
verifySelectiveBufferOutput(t, buf.String())
// Verify callback entries
verifySelectiveCallbackEntries(t, callbackEntries)
}
func verifySelectiveBufferOutput(t *testing.T, logOutput string) {
t.Helper()
if !strings.Contains(logOutput, "[enforce]") {
t.Errorf("Expected log output to contain enforce events")
}
if !strings.Contains(logOutput, "[addPolicy]") {
t.Errorf("Expected log output to contain addPolicy event")
}
if strings.Contains(logOutput, "[removePolicy]") {
t.Errorf("Did not expect log output to contain removePolicy event")
}
if strings.Contains(logOutput, "[loadPolicy]") {
t.Errorf("Did not expect log output to contain loadPolicy event")
}
}
func verifySelectiveCallbackEntries(t *testing.T, entries []*log.LogEntry) {
t.Helper()
found := map[log.EventType]bool{}
for _, entry := range entries {
found[entry.EventType] = true
checkEntryActiveStatus(t, entry)
}
requiredEvents := []log.EventType{
log.EventEnforce, log.EventAddPolicy, log.EventRemovePolicy, log.EventLoadPolicy,
}
for _, eventType := range requiredEvents {
if !found[eventType] {
t.Errorf("Expected to find %s in callback entries", eventType)
}
}
}
func checkEntryActiveStatus(t *testing.T, entry *log.LogEntry) {
t.Helper()
switch entry.EventType {
case log.EventEnforce, log.EventAddPolicy:
if !entry.IsActive {
t.Errorf("Expected %s entry to be active", entry.EventType)
}
case log.EventRemovePolicy, log.EventLoadPolicy:
if entry.IsActive {
t.Errorf("Expected %s entry to be inactive", entry.EventType)
}
case log.EventSavePolicy:
// SavePolicy event exists but we're not checking it in this test
}
}
================================================
FILE: management_api.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"errors"
"fmt"
"strings"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/util"
"github.com/casbin/govaluate"
)
// GetAllSubjects gets the list of subjects that show up in the current policy.
func (e *Enforcer) GetAllSubjects() ([]string, error) {
return e.model.GetValuesForFieldInPolicyAllTypesByName("p", constant.SubjectIndex)
}
// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.
func (e *Enforcer) GetAllNamedSubjects(ptype string) ([]string, error) {
fieldIndex, err := e.model.GetFieldIndex(ptype, constant.SubjectIndex)
if err != nil {
return nil, err
}
return e.model.GetValuesForFieldInPolicy("p", ptype, fieldIndex)
}
// GetAllObjects gets the list of objects that show up in the current policy.
func (e *Enforcer) GetAllObjects() ([]string, error) {
return e.model.GetValuesForFieldInPolicyAllTypesByName("p", constant.ObjectIndex)
}
// GetAllNamedObjects gets the list of objects that show up in the current named policy.
func (e *Enforcer) GetAllNamedObjects(ptype string) ([]string, error) {
fieldIndex, err := e.model.GetFieldIndex(ptype, constant.ObjectIndex)
if err != nil {
return nil, err
}
return e.model.GetValuesForFieldInPolicy("p", ptype, fieldIndex)
}
// GetAllActions gets the list of actions that show up in the current policy.
func (e *Enforcer) GetAllActions() ([]string, error) {
return e.model.GetValuesForFieldInPolicyAllTypesByName("p", constant.ActionIndex)
}
// GetAllNamedActions gets the list of actions that show up in the current named policy.
func (e *Enforcer) GetAllNamedActions(ptype string) ([]string, error) {
fieldIndex, err := e.model.GetFieldIndex(ptype, constant.ActionIndex)
if err != nil {
return nil, err
}
return e.model.GetValuesForFieldInPolicy("p", ptype, fieldIndex)
}
// GetAllRoles gets the list of roles that show up in the current policy.
func (e *Enforcer) GetAllRoles() ([]string, error) {
return e.model.GetValuesForFieldInPolicyAllTypes("g", 1)
}
// GetAllNamedRoles gets the list of roles that show up in the current named policy.
func (e *Enforcer) GetAllNamedRoles(ptype string) ([]string, error) {
return e.model.GetValuesForFieldInPolicy("g", ptype, 1)
}
// GetAllUsers gets the list of users that show up in the current policy.
// Users are subjects that are not roles (i.e., subjects that do not appear as the second element in any grouping policy).
func (e *Enforcer) GetAllUsers() ([]string, error) {
subjects, err := e.GetAllSubjects()
if err != nil {
return nil, err
}
roles, err := e.GetAllRoles()
if err != nil {
return nil, err
}
users := util.SetSubtract(subjects, roles)
return users, nil
}
// GetPolicy gets all the authorization rules in the policy.
func (e *Enforcer) GetPolicy() ([][]string, error) {
return e.GetNamedPolicy("p")
}
// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.
func (e *Enforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.GetFilteredNamedPolicy("p", fieldIndex, fieldValues...)
}
// GetNamedPolicy gets all the authorization rules in the named policy.
func (e *Enforcer) GetNamedPolicy(ptype string) ([][]string, error) {
return e.model.GetPolicy("p", ptype)
}
// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.
func (e *Enforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.model.GetFilteredPolicy("p", ptype, fieldIndex, fieldValues...)
}
// GetGroupingPolicy gets all the role inheritance rules in the policy.
func (e *Enforcer) GetGroupingPolicy() ([][]string, error) {
return e.GetNamedGroupingPolicy("g")
}
// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
func (e *Enforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.GetFilteredNamedGroupingPolicy("g", fieldIndex, fieldValues...)
}
// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.
func (e *Enforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {
return e.model.GetPolicy("g", ptype)
}
// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.
func (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.model.GetFilteredPolicy("g", ptype, fieldIndex, fieldValues...)
}
// GetFilteredNamedPolicyWithMatcher gets rules based on matcher from the policy.
func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher string) ([][]string, error) {
var res [][]string
var err error
functions := e.fm.GetFunctions()
if _, ok := e.model["g"]; ok {
for key, ast := range e.model["g"] {
// g must be a normal role definition (ast.RM != nil)
// or a conditional role definition (ast.CondRM != nil)
// ast.RM and ast.CondRM shouldn't be nil at the same time
if ast.RM != nil {
functions[key] = util.GenerateGFunction(ast.RM)
}
if ast.CondRM != nil {
functions[key] = util.GenerateConditionalGFunction(ast.CondRM)
}
}
}
var expString string
if matcher == "" {
return res, fmt.Errorf("matcher is empty")
} else {
expString = util.RemoveComments(util.EscapeAssertion(matcher))
}
var expression *govaluate.EvaluableExpression
expression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
if err != nil {
return res, err
}
pTokens := make(map[string]int, len(e.model["p"][ptype].Tokens))
for i, token := range e.model["p"][ptype].Tokens {
pTokens[token] = i
}
parameters := enforceParameters{
pTokens: pTokens,
}
if policyLen := len(e.model["p"][ptype].Policy); policyLen != 0 && strings.Contains(expString, ptype+"_") {
for _, pvals := range e.model["p"][ptype].Policy {
if len(e.model["p"][ptype].Tokens) != len(pvals) {
return res, fmt.Errorf(
"invalid policy size: expected %d, got %d, pvals: %v",
len(e.model["p"][ptype].Tokens),
len(pvals),
pvals)
}
parameters.pVals = pvals
result, err := expression.Eval(parameters)
if err != nil {
return res, err
}
switch result := result.(type) {
case bool:
if result {
res = append(res, pvals)
}
case float64:
if result != 0 {
res = append(res, pvals)
}
default:
return res, errors.New("matcher result should be bool, int or float")
}
}
}
return res, nil
}
// HasPolicy determines whether an authorization rule exists.
func (e *Enforcer) HasPolicy(params ...interface{}) (bool, error) {
return e.HasNamedPolicy("p", params...)
}
// HasNamedPolicy determines whether a named authorization rule exists.
func (e *Enforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
return e.model.HasPolicy("p", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.model.HasPolicy("p", ptype, policy)
}
// AddPolicy adds an authorization rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddPolicy(params ...interface{}) (bool, error) {
return e.AddNamedPolicy("p", params...)
}
// AddPolicies adds authorization rules to the current policy.
// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
// Otherwise the function returns true for the corresponding rule by adding the new rule.
func (e *Enforcer) AddPolicies(rules [][]string) (bool, error) {
return e.AddNamedPolicies("p", rules)
}
// AddPoliciesEx adds authorization rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddPolicies, other non-existent rules are added instead of returning false directly.
func (e *Enforcer) AddPoliciesEx(rules [][]string) (bool, error) {
return e.AddNamedPoliciesEx("p", rules)
}
// AddNamedPolicy adds an authorization rule to the current named policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
strSlice = append(make([]string, 0, len(strSlice)), strSlice...)
return e.addPolicy("p", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.addPolicy("p", ptype, policy)
}
// AddNamedPolicies adds authorization rules to the current named policy.
// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
// Otherwise the function returns true for the corresponding by adding the new rule.
func (e *Enforcer) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {
return e.addPolicies("p", ptype, rules, false)
}
// AddNamedPoliciesEx adds authorization rules to the current named policy.
// If the rule already exists, the rule will not be added.
// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly.
func (e *Enforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {
return e.addPolicies("p", ptype, rules, true)
}
// RemovePolicy removes an authorization rule from the current policy.
func (e *Enforcer) RemovePolicy(params ...interface{}) (bool, error) {
return e.RemoveNamedPolicy("p", params...)
}
// UpdatePolicy updates an authorization rule from the current policy.
func (e *Enforcer) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {
return e.UpdateNamedPolicy("p", oldPolicy, newPolicy)
}
func (e *Enforcer) UpdateNamedPolicy(ptype string, p1 []string, p2 []string) (bool, error) {
return e.updatePolicy("p", ptype, p1, p2)
}
// UpdatePolicies updates authorization rules from the current policies.
func (e *Enforcer) UpdatePolicies(oldPolices [][]string, newPolicies [][]string) (bool, error) {
return e.UpdateNamedPolicies("p", oldPolices, newPolicies)
}
func (e *Enforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]string) (bool, error) {
return e.updatePolicies("p", ptype, p1, p2)
}
func (e *Enforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.UpdateFilteredNamedPolicies("p", newPolicies, fieldIndex, fieldValues...)
}
func (e *Enforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.updateFilteredPolicies("p", ptype, newPolicies, fieldIndex, fieldValues...)
}
// RemovePolicies removes authorization rules from the current policy.
func (e *Enforcer) RemovePolicies(rules [][]string) (bool, error) {
return e.RemoveNamedPolicies("p", rules)
}
// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) {
return e.RemoveFilteredNamedPolicy("p", fieldIndex, fieldValues...)
}
// RemoveNamedPolicy removes an authorization rule from the current named policy.
func (e *Enforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
return e.removePolicy("p", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.removePolicy("p", ptype, policy)
}
// RemoveNamedPolicies removes authorization rules from the current named policy.
func (e *Enforcer) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {
return e.removePolicies("p", ptype, rules)
}
// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicy("p", ptype, fieldIndex, fieldValues)
}
// HasGroupingPolicy determines whether a role inheritance rule exists.
func (e *Enforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {
return e.HasNamedGroupingPolicy("g", params...)
}
// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.
func (e *Enforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
return e.model.HasPolicy("g", ptype, strSlice)
}
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
return e.model.HasPolicy("g", ptype, policy)
}
// AddGroupingPolicy adds a role inheritance rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddGroupingPolicy(params ...interface{}) (bool, error) {
return e.AddNamedGroupingPolicy("g", params...)
}
// AddGroupingPolicies adds role inheritance rules to the current policy.
// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
// Otherwise the function returns true for the corresponding policy rule by adding the new rule.
func (e *Enforcer) AddGroupingPolicies(rules [][]string) (bool, error) {
return e.AddNamedGroupingPolicies("g", rules)
}
// AddGroupingPoliciesEx adds role inheritance rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly.
func (e *Enforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {
return e.AddNamedGroupingPoliciesEx("g", rules)
}
// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
var ruleAdded bool
var err error
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
ruleAdded, err = e.addPolicy("g", ptype, strSlice)
} else {
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
ruleAdded, err = e.addPolicy("g", ptype, policy)
}
return ruleAdded, err
}
// AddNamedGroupingPolicies adds named role inheritance rules to the current policy.
// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
// Otherwise the function returns true for the corresponding policy rule by adding the new rule.
func (e *Enforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {
return e.addPolicies("g", ptype, rules, false)
}
// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
// If the rule already exists, the rule will not be added.
// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly.
func (e *Enforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {
return e.addPolicies("g", ptype, rules, true)
}
// RemoveGroupingPolicy removes a role inheritance rule from the current policy.
func (e *Enforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) {
return e.RemoveNamedGroupingPolicy("g", params...)
}
// RemoveGroupingPolicies removes role inheritance rules from the current policy.
func (e *Enforcer) RemoveGroupingPolicies(rules [][]string) (bool, error) {
return e.RemoveNamedGroupingPolicies("g", rules)
}
// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) {
return e.RemoveFilteredNamedGroupingPolicy("g", fieldIndex, fieldValues...)
}
// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy.
func (e *Enforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
var ruleRemoved bool
var err error
if strSlice, ok := params[0].([]string); len(params) == 1 && ok {
ruleRemoved, err = e.removePolicy("g", ptype, strSlice)
} else {
policy := make([]string, 0)
for _, param := range params {
policy = append(policy, param.(string))
}
ruleRemoved, err = e.removePolicy("g", ptype, policy)
}
return ruleRemoved, err
}
// RemoveNamedGroupingPolicies removes role inheritance rules from the current named policy.
func (e *Enforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {
return e.removePolicies("g", ptype, rules)
}
func (e *Enforcer) UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error) {
return e.UpdateNamedGroupingPolicy("g", oldRule, newRule)
}
// UpdateGroupingPolicies updates authorization rules from the current policies.
func (e *Enforcer) UpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error) {
return e.UpdateNamedGroupingPolicies("g", oldRules, newRules)
}
func (e *Enforcer) UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error) {
return e.updatePolicy("g", ptype, oldRule, newRule)
}
func (e *Enforcer) UpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error) {
return e.updatePolicies("g", ptype, oldRules, newRules)
}
// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicy("g", ptype, fieldIndex, fieldValues)
}
// AddFunction adds a customized function.
func (e *Enforcer) AddFunction(name string, function govaluate.ExpressionFunction) {
e.fm.AddFunction(name, function)
}
func (e *Enforcer) SelfAddPolicy(sec string, ptype string, rule []string) (bool, error) {
return e.addPolicyWithoutNotify(sec, ptype, rule)
}
func (e *Enforcer) SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesWithoutNotify(sec, ptype, rules, false)
}
func (e *Enforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {
return e.addPoliciesWithoutNotify(sec, ptype, rules, true)
}
func (e *Enforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {
return e.removePolicyWithoutNotify(sec, ptype, rule)
}
func (e *Enforcer) SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {
return e.removePoliciesWithoutNotify(sec, ptype, rules)
}
func (e *Enforcer) SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex, fieldValues)
}
func (e *Enforcer) SelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error) {
return e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)
}
func (e *Enforcer) SelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
return e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)
}
================================================
FILE: management_api_b_test.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"math/rand"
"testing"
)
func BenchmarkHasPolicySmall(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 100 roles, 10 resources.
for i := 0; i < 100; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
e.HasPolicy(fmt.Sprintf("user%d", rand.Intn(100)), fmt.Sprintf("data%d", rand.Intn(100)/10), "read")
}
}
func BenchmarkHasPolicyMedium(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
e.HasPolicy(fmt.Sprintf("user%d", rand.Intn(1000)), fmt.Sprintf("data%d", rand.Intn(1000)/10), "read")
}
}
func BenchmarkHasPolicyLarge(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
e.HasPolicy(fmt.Sprintf("user%d", rand.Intn(10000)), fmt.Sprintf("data%d", rand.Intn(10000)/10), "read")
}
}
func BenchmarkAddPolicySmall(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 100 roles, 10 resources.
for i := 0; i < 100; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", rand.Intn(100)+100), fmt.Sprintf("data%d", (rand.Intn(100)+100)/10), "read")
}
}
func BenchmarkAddPolicyMedium(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", rand.Intn(1000)+1000), fmt.Sprintf("data%d", (rand.Intn(1000)+1000)/10), "read")
}
}
func BenchmarkAddPolicyLarge(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", rand.Intn(10000)+10000), fmt.Sprintf("data%d", (rand.Intn(10000)+10000)/10), "read")
}
}
func BenchmarkRemovePolicySmall(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 100 roles, 10 resources.
for i := 0; i < 100; i++ {
_, _ = e.AddPolicy(fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.RemovePolicy(fmt.Sprintf("user%d", rand.Intn(100)), fmt.Sprintf("data%d", rand.Intn(100)/10), "read")
}
}
func BenchmarkRemovePolicyMedium(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.RemovePolicy(fmt.Sprintf("user%d", rand.Intn(1000)), fmt.Sprintf("data%d", rand.Intn(1000)/10), "read")
}
}
func BenchmarkRemovePolicyLarge(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.RemovePolicy(fmt.Sprintf("user%d", rand.Intn(10000)), fmt.Sprintf("data%d", rand.Intn(10000)/10), "read")
}
}
================================================
FILE: management_api_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
"github.com/casbin/casbin/v3/util"
)
func testStringList(t *testing.T, title string, f func() ([]string, error), res []string) {
t.Helper()
myRes, err := f()
if err != nil {
t.Error(err)
}
t.Log(title+": ", myRes)
if !util.ArrayEquals(res, myRes) {
t.Error(title+": ", myRes, ", supposed to be ", res)
}
}
func TestGetList(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testStringList(t, "Subjects", e.GetAllSubjects, []string{"alice", "bob", "data2_admin"})
testStringList(t, "Objects", e.GetAllObjects, []string{"data1", "data2"})
testStringList(t, "Actions", e.GetAllActions, []string{"read", "write"})
testStringList(t, "Roles", e.GetAllRoles, []string{"data2_admin"})
testStringList(t, "Users", e.GetAllUsers, []string{"alice", "bob"})
}
func TestGetListWithDomains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testStringList(t, "Subjects", e.GetAllSubjects, []string{"admin"})
testStringList(t, "Objects", e.GetAllObjects, []string{"data1", "data2"})
testStringList(t, "Actions", e.GetAllActions, []string{"read", "write"})
testStringList(t, "Roles", e.GetAllRoles, []string{"admin"})
testStringList(t, "Users", e.GetAllUsers, []string{})
}
func testGetPolicy(t *testing.T, e *Enforcer, res [][]string) {
t.Helper()
myRes, err := e.GetPolicy()
if err != nil {
t.Error(err)
}
t.Log("Policy: ", myRes)
if !util.SortedArray2DEquals(res, myRes) {
t.Error("Policy: ", myRes, ", supposed to be ", res)
}
}
func testGetFilteredPolicy(t *testing.T, e *Enforcer, fieldIndex int, res [][]string, fieldValues ...string) {
t.Helper()
myRes, err := e.GetFilteredPolicy(fieldIndex, fieldValues...)
if err != nil {
t.Error(err)
}
t.Log("Policy for ", util.ParamsToString(fieldValues...), ": ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Policy for ", util.ParamsToString(fieldValues...), ": ", myRes, ", supposed to be ", res)
}
}
func testGetFilteredNamedPolicyWithMatcher(t *testing.T, e *Enforcer, ptype string, matcher string, res [][]string) {
t.Helper()
myRes, err := e.GetFilteredNamedPolicyWithMatcher(ptype, matcher)
t.Log("Policy for", matcher, ": ", myRes)
if err != nil {
t.Error(err)
}
if !util.Array2DEquals(res, myRes) {
t.Error("Policy for ", matcher, ": ", myRes, ", supposed to be ", res)
}
}
func testGetGroupingPolicy(t *testing.T, e *Enforcer, res [][]string) {
t.Helper()
myRes, err := e.GetGroupingPolicy()
if err != nil {
t.Error(err)
}
t.Log("Grouping policy: ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Grouping policy: ", myRes, ", supposed to be ", res)
}
}
func testGetFilteredGroupingPolicy(t *testing.T, e *Enforcer, fieldIndex int, res [][]string, fieldValues ...string) {
t.Helper()
myRes, err := e.GetFilteredGroupingPolicy(fieldIndex, fieldValues...)
if err != nil {
t.Error(err)
}
t.Log("Grouping policy for ", util.ParamsToString(fieldValues...), ": ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Grouping policy for ", util.ParamsToString(fieldValues...), ": ", myRes, ", supposed to be ", res)
}
}
func testHasPolicy(t *testing.T, e *Enforcer, policy []string, res bool) {
t.Helper()
myRes, err := e.HasPolicy(policy)
if err != nil {
t.Error(err)
}
t.Log("Has policy ", util.ArrayToString(policy), ": ", myRes)
if res != myRes {
t.Error("Has policy ", util.ArrayToString(policy), ": ", myRes, ", supposed to be ", res)
}
}
func testHasGroupingPolicy(t *testing.T, e *Enforcer, policy []string, res bool) {
t.Helper()
myRes, err := e.HasGroupingPolicy(policy)
if err != nil {
t.Error(err)
}
t.Log("Has grouping policy ", util.ArrayToString(policy), ": ", myRes)
if res != myRes {
t.Error("Has grouping policy ", util.ArrayToString(policy), ": ", myRes, ", supposed to be ", res)
}
}
func TestGetPolicyAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"}})
testGetFilteredPolicy(t, e, 0, [][]string{{"alice", "data1", "read"}}, "alice")
testGetFilteredPolicy(t, e, 0, [][]string{{"bob", "data2", "write"}}, "bob")
testGetFilteredPolicy(t, e, 0, [][]string{{"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}, "data2_admin")
testGetFilteredPolicy(t, e, 1, [][]string{{"alice", "data1", "read"}}, "data1")
testGetFilteredPolicy(t, e, 1, [][]string{{"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}, "data2")
testGetFilteredPolicy(t, e, 2, [][]string{{"alice", "data1", "read"}, {"data2_admin", "data2", "read"}}, "read")
testGetFilteredPolicy(t, e, 2, [][]string{{"bob", "data2", "write"}, {"data2_admin", "data2", "write"}}, "write")
testGetFilteredNamedPolicyWithMatcher(t, e, "p", "'alice' == p.sub", [][]string{{"alice", "data1", "read"}})
testGetFilteredNamedPolicyWithMatcher(t, e, "p", "keyMatch2(p.sub, '*')", [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"}})
testGetFilteredPolicy(t, e, 0, [][]string{{"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}}, "data2_admin", "data2")
// Note: "" (empty string) in fieldValues means matching all values.
testGetFilteredPolicy(t, e, 0, [][]string{{"data2_admin", "data2", "read"}}, "data2_admin", "", "read")
testGetFilteredPolicy(t, e, 1, [][]string{{"bob", "data2", "write"}, {"data2_admin", "data2", "write"}}, "data2", "write")
testHasPolicy(t, e, []string{"alice", "data1", "read"}, true)
testHasPolicy(t, e, []string{"bob", "data2", "write"}, true)
testHasPolicy(t, e, []string{"alice", "data2", "read"}, false)
testHasPolicy(t, e, []string{"bob", "data3", "write"}, false)
testGetGroupingPolicy(t, e, [][]string{{"alice", "data2_admin"}})
testGetFilteredGroupingPolicy(t, e, 0, [][]string{{"alice", "data2_admin"}}, "alice")
testGetFilteredGroupingPolicy(t, e, 0, [][]string{}, "bob")
testGetFilteredGroupingPolicy(t, e, 1, [][]string{}, "data1_admin")
testGetFilteredGroupingPolicy(t, e, 1, [][]string{{"alice", "data2_admin"}}, "data2_admin")
// Note: "" (empty string) in fieldValues means matching all values.
testGetFilteredGroupingPolicy(t, e, 0, [][]string{{"alice", "data2_admin"}}, "", "data2_admin")
testHasGroupingPolicy(t, e, []string{"alice", "data2_admin"}, true)
testHasGroupingPolicy(t, e, []string{"bob", "data2_admin"}, false)
}
func TestModifyPolicyAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testGetPolicy(t, e, [][]string{
{"alice", "data1", "read"},
{"bob", "data2", "write"},
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"}})
_, _ = e.RemovePolicy("alice", "data1", "read")
_, _ = e.RemovePolicy("bob", "data2", "write")
_, _ = e.RemovePolicy("alice", "data1", "read")
_, _ = e.AddPolicy("eve", "data3", "read")
_, _ = e.AddPolicy("eve", "data3", "read")
rules := [][]string{
{"jack", "data4", "read"},
{"jack", "data4", "read"},
{"jack", "data4", "read"},
{"katy", "data4", "write"},
{"leyo", "data4", "read"},
{"katy", "data4", "write"},
{"katy", "data4", "write"},
{"ham", "data4", "write"},
}
_, _ = e.AddPolicies(rules)
_, _ = e.AddPolicies(rules)
testGetPolicy(t, e, [][]string{
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"},
{"eve", "data3", "read"},
{"jack", "data4", "read"},
{"katy", "data4", "write"},
{"leyo", "data4", "read"},
{"ham", "data4", "write"}})
_, _ = e.RemovePolicies(rules)
_, _ = e.RemovePolicies(rules)
namedPolicy := []string{"eve", "data3", "read"}
_, _ = e.RemoveNamedPolicy("p", namedPolicy)
_, _ = e.AddNamedPolicy("p", namedPolicy)
testGetPolicy(t, e, [][]string{
{"data2_admin", "data2", "read"},
{"data2_admin", "data2", "write"},
{"eve", "data3", "read"}})
_, _ = e.RemoveFilteredPolicy(1, "data2")
testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}})
_, _ = e.UpdatePolicy([]string{"eve", "data3", "read"}, []string{"eve", "data3", "write"})
testGetPolicy(t, e, [][]string{{"eve", "data3", "write"}})
// This test shows a rollback effect.
// _, _ = e.UpdatePolicies([][]string{{"eve", "data3", "write"}, {"jack", "data4", "read"}}, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "write"}})
// testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "write"}})
_, _ = e.AddPolicies(rules)
_, _ = e.UpdatePolicies([][]string{{"eve", "data3", "write"}, {"leyo", "data4", "read"}, {"katy", "data4", "write"}},
[][]string{{"eve", "data3", "read"}, {"leyo", "data4", "write"}, {"katy", "data1", "write"}})
testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "read"}, {"katy", "data1", "write"}, {"leyo", "data4", "write"}, {"ham", "data4", "write"}})
e.ClearPolicy()
_, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user1", "data1", "read"}})
testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}})
// {"user1", "data1", "read"} repeated
_, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
// {"user1", "data1", "read"}, {"user2", "data2", "read"} repeated
_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}})
testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}})
// {"user1", "data1", "read"}, {"user2", "data2", "read"}, , {"user3", "data3", "read"} repeated
_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}, {"user4", "data4", "read"}})
testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}, {"user4", "data4", "read"}})
}
func TestModifyGroupingPolicyAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testGetRoles(t, e, []string{"data2_admin"}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "eve")
testGetRoles(t, e, []string{}, "non_exist")
_, _ = e.RemoveGroupingPolicy("alice", "data2_admin")
_, _ = e.AddGroupingPolicy("bob", "data1_admin")
_, _ = e.AddGroupingPolicy("eve", "data3_admin")
groupingRules := [][]string{
{"ham", "data4_admin"},
{"jack", "data5_admin"},
}
_, _ = e.AddGroupingPolicies(groupingRules)
testGetRoles(t, e, []string{"data4_admin"}, "ham")
testGetRoles(t, e, []string{"data5_admin"}, "jack")
_, _ = e.RemoveGroupingPolicies(groupingRules)
testGetRoles(t, e, []string{}, "alice")
namedGroupingPolicy := []string{"alice", "data2_admin"}
testGetRoles(t, e, []string{}, "alice")
_, _ = e.AddNamedGroupingPolicy("g", namedGroupingPolicy)
testGetRoles(t, e, []string{"data2_admin"}, "alice")
_, _ = e.RemoveNamedGroupingPolicy("g", namedGroupingPolicy)
_, _ = e.AddNamedGroupingPolicies("g", groupingRules)
_, _ = e.AddNamedGroupingPolicies("g", groupingRules)
testGetRoles(t, e, []string{"data4_admin"}, "ham")
testGetRoles(t, e, []string{"data5_admin"}, "jack")
_, _ = e.RemoveNamedGroupingPolicies("g", groupingRules)
_, _ = e.RemoveNamedGroupingPolicies("g", groupingRules)
testGetRoles(t, e, []string{}, "alice")
testGetRoles(t, e, []string{"data1_admin"}, "bob")
testGetRoles(t, e, []string{"data3_admin"}, "eve")
testGetRoles(t, e, []string{}, "non_exist")
testGetUsers(t, e, []string{"bob"}, "data1_admin")
testGetUsers(t, e, []string{}, "data2_admin")
testGetUsers(t, e, []string{"eve"}, "data3_admin")
_, _ = e.RemoveFilteredGroupingPolicy(0, "bob")
testGetRoles(t, e, []string{}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{"data3_admin"}, "eve")
testGetRoles(t, e, []string{}, "non_exist")
testGetUsers(t, e, []string{}, "data1_admin")
testGetUsers(t, e, []string{}, "data2_admin")
testGetUsers(t, e, []string{"eve"}, "data3_admin")
_, _ = e.AddGroupingPolicy("data3_admin", "data4_admin")
_, _ = e.UpdateGroupingPolicy([]string{"eve", "data3_admin"}, []string{"eve", "admin"})
_, _ = e.UpdateGroupingPolicy([]string{"data3_admin", "data4_admin"}, []string{"admin", "data4_admin"})
testGetUsers(t, e, []string{"admin"}, "data4_admin")
testGetUsers(t, e, []string{"eve"}, "admin")
testGetRoles(t, e, []string{"admin"}, "eve")
testGetRoles(t, e, []string{"data4_admin"}, "admin")
_, _ = e.UpdateGroupingPolicies([][]string{{"eve", "admin"}}, [][]string{{"eve", "admin_groups"}})
_, _ = e.UpdateGroupingPolicies([][]string{{"admin", "data4_admin"}}, [][]string{{"admin", "data5_admin"}})
testGetUsers(t, e, []string{"admin"}, "data5_admin")
testGetUsers(t, e, []string{"eve"}, "admin_groups")
testGetRoles(t, e, []string{"data5_admin"}, "admin")
testGetRoles(t, e, []string{"admin_groups"}, "eve")
e.ClearPolicy()
_, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}})
testGetUsers(t, e, []string{"user1"}, "member")
// {"user1", "member"} repeated
_, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}})
testGetUsers(t, e, []string{"user1", "user2"}, "member")
// {"user1", "member"}, {"user2", "member"} repeated
_, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}, {"user3", "member"}})
testGetUsers(t, e, []string{"user1", "user2", "user3"}, "member")
}
================================================
FILE: model/assertion.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"errors"
"strings"
"sync"
"github.com/casbin/casbin/v3/rbac"
)
// Assertion represents an expression in a section of the model.
// For example: r = sub, obj, act.
type Assertion struct {
Key string
Value string
Tokens []string
ParamsTokens []string
Policy [][]string
PolicyMap map[string]int
RM rbac.RoleManager
CondRM rbac.ConditionalRoleManager
FieldIndexMap map[string]int
FieldIndexMutex sync.RWMutex
}
func (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, rules [][]string) error {
ast.RM = rm
count := strings.Count(ast.Value, "_")
if count < 2 {
return errors.New("the number of \"_\" in role definition should be at least 2")
}
for _, rule := range rules {
if len(rule) < count {
return errors.New("grouping policy elements do not meet role definition")
}
if len(rule) > count {
rule = rule[:count]
}
switch op {
case PolicyAdd:
err := rm.AddLink(rule[0], rule[1], rule[2:]...)
if err != nil {
return err
}
case PolicyRemove:
err := rm.DeleteLink(rule[0], rule[1], rule[2:]...)
if err != nil {
return err
}
}
}
return nil
}
func (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error {
ast.RM = rm
count := strings.Count(ast.Value, "_")
if count < 2 {
return errors.New("the number of \"_\" in role definition should be at least 2")
}
for _, rule := range ast.Policy {
if len(rule) < count {
return errors.New("grouping policy elements do not meet role definition")
}
if len(rule) > count {
rule = rule[:count]
}
err := ast.RM.AddLink(rule[0], rule[1], rule[2:]...)
if err != nil {
return err
}
}
return nil
}
func (ast *Assertion) buildIncrementalConditionalRoleLinks(condRM rbac.ConditionalRoleManager, op PolicyOp, rules [][]string) error {
ast.CondRM = condRM
count := strings.Count(ast.Value, "_")
if count < 2 {
return errors.New("the number of \"_\" in role definition should be at least 2")
}
for _, rule := range rules {
if len(rule) < count {
return errors.New("grouping policy elements do not meet role definition")
}
if len(rule) > count {
rule = rule[:count]
}
var err error
domainRule := rule[2:len(ast.Tokens)]
switch op {
case PolicyAdd:
err = ast.addConditionalRoleLink(rule, domainRule)
case PolicyRemove:
err = ast.CondRM.DeleteLink(rule[0], rule[1], rule[2:]...)
}
if err != nil {
return err
}
}
return nil
}
func (ast *Assertion) buildConditionalRoleLinks(condRM rbac.ConditionalRoleManager) error {
ast.CondRM = condRM
count := strings.Count(ast.Value, "_")
if count < 2 {
return errors.New("the number of \"_\" in role definition should be at least 2")
}
for _, rule := range ast.Policy {
if len(rule) < count {
return errors.New("grouping policy elements do not meet role definition")
}
if len(rule) > count {
rule = rule[:count]
}
domainRule := rule[2:len(ast.Tokens)]
err := ast.addConditionalRoleLink(rule, domainRule)
if err != nil {
return err
}
}
return nil
}
// addConditionalRoleLink adds Link to rbac.ConditionalRoleManager and sets the parameters for LinkConditionFunc.
func (ast *Assertion) addConditionalRoleLink(rule []string, domainRule []string) error {
var err error
if len(domainRule) == 0 {
err = ast.CondRM.AddLink(rule[0], rule[1])
if err == nil {
ast.CondRM.SetLinkConditionFuncParams(rule[0], rule[1], rule[len(ast.Tokens):]...)
}
} else {
domain := domainRule[0]
err = ast.CondRM.AddLink(rule[0], rule[1], domain)
if err == nil {
ast.CondRM.SetDomainLinkConditionFuncParams(rule[0], rule[1], domain, rule[len(ast.Tokens):]...)
}
}
return err
}
func (ast *Assertion) copy() *Assertion {
tokens := append([]string(nil), ast.Tokens...)
policy := make([][]string, len(ast.Policy))
for i, p := range ast.Policy {
policy[i] = append(policy[i], p...)
}
policyMap := make(map[string]int)
for k, v := range ast.PolicyMap {
policyMap[k] = v
}
ast.FieldIndexMutex.RLock()
fieldIndexMap := make(map[string]int)
for k, v := range ast.FieldIndexMap {
fieldIndexMap[k] = v
}
ast.FieldIndexMutex.RUnlock()
newAst := &Assertion{
Key: ast.Key,
Value: ast.Value,
PolicyMap: policyMap,
Tokens: tokens,
Policy: policy,
FieldIndexMap: fieldIndexMap,
ParamsTokens: append([]string(nil), ast.ParamsTokens...),
RM: ast.RM,
CondRM: ast.CondRM,
}
return newAst
}
================================================
FILE: model/constraint.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/casbin/casbin/v3/errors"
)
// ConstraintType represents the type of constraint.
type ConstraintType int
const (
ConstraintTypeSOD ConstraintType = iota
ConstraintTypeSODMax
ConstraintTypeRoleMax
ConstraintTypeRolePre
)
// Constraint represents a policy constraint.
type Constraint struct {
Key string
Type ConstraintType
Roles []string
Role string
MaxCount int
PreReqRole string
}
var (
// Regex patterns for parsing constraints (compiled once at package initialization).
sodPattern = regexp.MustCompile(`^sod\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)$`)
sodMaxPattern = regexp.MustCompile(`^sodMax\s*\(\s*\[([^\]]+)\]\s*,\s*(\d+)\s*\)$`)
roleMaxPattern = regexp.MustCompile(`^roleMax\s*\(\s*"([^"]+)"\s*,\s*(\d+)\s*\)$`)
rolePrePattern = regexp.MustCompile(`^rolePre\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)$`)
)
// parseRolesArray parses a comma-separated string of quoted role names.
func parseRolesArray(rolesStr string) ([]string, error) {
var roles []string
for _, role := range strings.Split(rolesStr, ",") {
role = strings.TrimSpace(role)
role = strings.Trim(role, `"`)
if role != "" {
roles = append(roles, role)
}
}
if len(roles) == 0 {
return nil, fmt.Errorf("no roles found in role array")
}
return roles, nil
}
// parseConstraint parses a constraint definition string.
func parseConstraint(key, value string) (*Constraint, error) {
value = strings.TrimSpace(value)
// Try to match sod pattern
if matches := sodPattern.FindStringSubmatch(value); matches != nil {
return &Constraint{
Key: key,
Type: ConstraintTypeSOD,
Roles: []string{matches[1], matches[2]},
}, nil
}
// Try to match sodMax pattern
if matches := sodMaxPattern.FindStringSubmatch(value); matches != nil {
maxCount, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("invalid max count in sodMax: %w", err)
}
roles, err := parseRolesArray(matches[1])
if err != nil {
return nil, fmt.Errorf("sodMax: %w", err)
}
return &Constraint{
Key: key,
Type: ConstraintTypeSODMax,
Roles: roles,
MaxCount: maxCount,
}, nil
}
// Try to match roleMax pattern
if matches := roleMaxPattern.FindStringSubmatch(value); matches != nil {
maxCount, err := strconv.Atoi(matches[2])
if err != nil {
return nil, fmt.Errorf("invalid max count in roleMax: %w", err)
}
return &Constraint{
Key: key,
Type: ConstraintTypeRoleMax,
Role: matches[1],
MaxCount: maxCount,
}, nil
}
// Try to match rolePre pattern
if matches := rolePrePattern.FindStringSubmatch(value); matches != nil {
return &Constraint{
Key: key,
Type: ConstraintTypeRolePre,
Role: matches[1],
PreReqRole: matches[2],
}, nil
}
return nil, fmt.Errorf("unrecognized constraint format: %s", value)
}
// ValidateConstraints validates all constraints against the current policy.
func (model Model) ValidateConstraints() error {
// Check if constraints exist
if model["c"] == nil || len(model["c"]) == 0 {
return nil // No constraints to validate
}
// Check if RBAC is enabled
if model["g"] == nil || len(model["g"]) == 0 {
return errors.ErrConstraintRequiresRBAC
}
// Get grouping policy
gAssertion := model["g"]["g"]
if gAssertion == nil {
return errors.ErrConstraintRequiresRBAC
}
// Validate each constraint
for _, assertion := range model["c"] {
constraint, err := parseConstraint(assertion.Key, assertion.Value)
if err != nil {
return fmt.Errorf("%w: %s", errors.ErrConstraintParsingError, err.Error())
}
if err := model.validateConstraint(constraint, gAssertion.Policy); err != nil {
return err
}
}
return nil
}
// validateConstraint validates a single constraint against the policy.
func (model Model) validateConstraint(constraint *Constraint, groupingPolicy [][]string) error {
switch constraint.Type {
case ConstraintTypeSOD:
return model.validateSOD(constraint, groupingPolicy)
case ConstraintTypeSODMax:
return model.validateSODMax(constraint, groupingPolicy)
case ConstraintTypeRoleMax:
return model.validateRoleMax(constraint, groupingPolicy)
case ConstraintTypeRolePre:
return model.validateRolePre(constraint, groupingPolicy)
default:
return fmt.Errorf("unknown constraint type")
}
}
// buildUserRoleMap builds a map of users to their assigned roles from grouping policy.
func buildUserRoleMap(groupingPolicy [][]string) map[string]map[string]bool {
userRoles := make(map[string]map[string]bool)
for _, rule := range groupingPolicy {
if len(rule) < 2 {
continue
}
user := rule[0]
role := rule[1]
if userRoles[user] == nil {
userRoles[user] = make(map[string]bool)
}
userRoles[user][role] = true
}
return userRoles
}
// validateSOD validates a Separation of Duties constraint.
func (model Model) validateSOD(constraint *Constraint, groupingPolicy [][]string) error {
if len(constraint.Roles) != 2 {
return errors.NewConstraintViolationError(constraint.Key, "sod requires exactly 2 roles")
}
role1, role2 := constraint.Roles[0], constraint.Roles[1]
userRoles := buildUserRoleMap(groupingPolicy)
// Check if any user has both roles
for user, roles := range userRoles {
if roles[role1] && roles[role2] {
return errors.NewConstraintViolationError(constraint.Key,
fmt.Sprintf("user '%s' cannot have both roles '%s' and '%s'", user, role1, role2))
}
}
return nil
}
// validateSODMax validates a maximum role count constraint for a role set.
func (model Model) validateSODMax(constraint *Constraint, groupingPolicy [][]string) error {
userRoles := buildUserRoleMap(groupingPolicy)
// Check if any user has more than maxCount roles from the role set
for user, roles := range userRoles {
count := 0
for _, role := range constraint.Roles {
if roles[role] {
count++
}
}
if count > constraint.MaxCount {
return errors.NewConstraintViolationError(constraint.Key,
fmt.Sprintf("user '%s' has %d roles from %v, exceeds maximum of %d",
user, count, constraint.Roles, constraint.MaxCount))
}
}
return nil
}
// validateRoleMax validates a role cardinality constraint.
func (model Model) validateRoleMax(constraint *Constraint, groupingPolicy [][]string) error {
roleCount := 0
// Count how many users have this role
for _, rule := range groupingPolicy {
if len(rule) < 2 {
continue
}
role := rule[1]
if role == constraint.Role {
roleCount++
}
}
if roleCount > constraint.MaxCount {
return errors.NewConstraintViolationError(constraint.Key,
fmt.Sprintf("role '%s' assigned to %d users, exceeds maximum of %d",
constraint.Role, roleCount, constraint.MaxCount))
}
return nil
}
// validateRolePre validates a prerequisite role constraint.
func (model Model) validateRolePre(constraint *Constraint, groupingPolicy [][]string) error {
userRoles := buildUserRoleMap(groupingPolicy)
// Check if any user has the main role without the prerequisite role
for user, roles := range userRoles {
if roles[constraint.Role] && !roles[constraint.PreReqRole] {
return errors.NewConstraintViolationError(constraint.Key,
fmt.Sprintf("user '%s' has role '%s' but lacks prerequisite role '%s'",
user, constraint.Role, constraint.PreReqRole))
}
}
return nil
}
================================================
FILE: model/function.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"sync"
"github.com/casbin/casbin/v3/util"
"github.com/casbin/govaluate"
)
// FunctionMap represents the collection of Function.
type FunctionMap struct {
fns *sync.Map
}
// [string]govaluate.ExpressionFunction
// AddFunction adds an expression function.
func (fm *FunctionMap) AddFunction(name string, function govaluate.ExpressionFunction) {
fm.fns.LoadOrStore(name, function)
}
// LoadFunctionMap loads an initial function map.
func LoadFunctionMap() FunctionMap {
fm := &FunctionMap{}
fm.fns = &sync.Map{}
fm.AddFunction("keyMatch", util.KeyMatchFunc)
fm.AddFunction("keyGet", util.KeyGetFunc)
fm.AddFunction("keyMatch2", util.KeyMatch2Func)
fm.AddFunction("keyGet2", util.KeyGet2Func)
fm.AddFunction("keyMatch3", util.KeyMatch3Func)
fm.AddFunction("keyGet3", util.KeyGet3Func)
fm.AddFunction("keyMatch4", util.KeyMatch4Func)
fm.AddFunction("keyMatch5", util.KeyMatch5Func)
fm.AddFunction("regexMatch", util.RegexMatchFunc)
fm.AddFunction("ipMatch", util.IPMatchFunc)
fm.AddFunction("globMatch", util.GlobMatchFunc)
return *fm
}
// GetFunctions return a map with all the functions.
func (fm *FunctionMap) GetFunctions() map[string]govaluate.ExpressionFunction {
ret := make(map[string]govaluate.ExpressionFunction)
fm.fns.Range(func(k interface{}, v interface{}) bool {
ret[k.(string)] = v.(govaluate.ExpressionFunction)
return true
})
return ret
}
================================================
FILE: model/model.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"container/list"
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/casbin/casbin/v3/config"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/util"
)
// Model represents the whole access control model.
type Model map[string]AssertionMap
// AssertionMap is the collection of assertions, can be "r", "p", "g", "e", "m".
type AssertionMap map[string]*Assertion
const defaultDomain string = ""
const defaultSeparator = "::"
var sectionNameMap = map[string]string{
"r": "request_definition",
"p": "policy_definition",
"g": "role_definition",
"e": "policy_effect",
"m": "matchers",
"c": "constraint_definition",
}
// Minimal required sections for a model to be valid.
var requiredSections = []string{"r", "p", "e", "m"}
func loadAssertion(model Model, cfg config.ConfigInterface, sec string, key string) bool {
value := cfg.String(sectionNameMap[sec] + "::" + key)
return model.AddDef(sec, key, value)
}
var paramsRegex = regexp.MustCompile(`\((.*?)\)`)
// getParamsToken Get ParamsToken from Assertion.Value.
func getParamsToken(value string) []string {
paramsString := paramsRegex.FindString(value)
if paramsString == "" {
return nil
}
paramsString = strings.TrimSuffix(strings.TrimPrefix(paramsString, "("), ")")
return strings.Split(paramsString, ",")
}
// AddDef adds an assertion to the model.
func (model Model) AddDef(sec string, key string, value string) bool {
if value == "" {
return false
}
ast := Assertion{}
ast.Key = key
ast.Value = value
ast.PolicyMap = make(map[string]int)
ast.FieldIndexMap = make(map[string]int)
if sec == "r" || sec == "p" {
ast.Tokens = strings.Split(ast.Value, ",")
for i := range ast.Tokens {
ast.Tokens[i] = key + "_" + strings.TrimSpace(ast.Tokens[i])
}
} else if sec == "g" {
ast.ParamsTokens = getParamsToken(ast.Value)
ast.Tokens = strings.Split(ast.Value, ",")
ast.Tokens = ast.Tokens[:len(ast.Tokens)-len(ast.ParamsTokens)]
} else {
ast.Value = util.RemoveComments(util.EscapeAssertion(ast.Value))
}
if sec == "m" {
// Escape backslashes in string literals to match CSV parsing behavior
ast.Value = util.EscapeStringLiterals(ast.Value)
if strings.Contains(ast.Value, "in") {
ast.Value = strings.Replace(strings.Replace(ast.Value, "[", "(", -1), "]", ")", -1)
}
}
_, ok := model[sec]
if !ok {
model[sec] = make(AssertionMap)
}
model[sec][key] = &ast
return true
}
func getKeySuffix(i int) string {
if i == 1 {
return ""
}
return strconv.Itoa(i)
}
func loadSection(model Model, cfg config.ConfigInterface, sec string) {
i := 1
for {
if !loadAssertion(model, cfg, sec, sec+getKeySuffix(i)) {
break
} else {
i++
}
}
}
// NewModel creates an empty model.
func NewModel() Model {
m := make(Model)
return m
}
// NewModelFromFile creates a model from a .CONF file.
func NewModelFromFile(path string) (Model, error) {
m := NewModel()
err := m.LoadModel(path)
if err != nil {
return nil, err
}
return m, nil
}
// NewModelFromString creates a model from a string which contains model text.
func NewModelFromString(text string) (Model, error) {
m := NewModel()
err := m.LoadModelFromText(text)
if err != nil {
return nil, err
}
return m, nil
}
// LoadModel loads the model from model CONF file.
func (model Model) LoadModel(path string) error {
cfg, err := config.NewConfig(path)
if err != nil {
return err
}
return model.loadModelFromConfig(cfg)
}
// LoadModelFromText loads the model from the text.
func (model Model) LoadModelFromText(text string) error {
cfg, err := config.NewConfigFromText(text)
if err != nil {
return err
}
return model.loadModelFromConfig(cfg)
}
// loadModelFromConfig loads the model from a config interface.
// It loads all sections defined in sectionNameMap and validates that required sections are present.
// If constraint_definition section exists, it validates all constraints against the current policy.
// Note: Constraint validation is performed during model loading, which may affect loading performance
// and can cause model loading to fail if constraints are violated or invalid.
func (model Model) loadModelFromConfig(cfg config.ConfigInterface) error {
for s := range sectionNameMap {
loadSection(model, cfg, s)
}
ms := make([]string, 0)
for _, rs := range requiredSections {
if !model.hasSection(rs) {
ms = append(ms, sectionNameMap[rs])
}
}
if len(ms) > 0 {
return fmt.Errorf("missing required sections: %s", strings.Join(ms, ","))
}
// Validate constraints after model is loaded
if err := model.ValidateConstraints(); err != nil {
return err
}
return nil
}
func (model Model) hasSection(sec string) bool {
section := model[sec]
return section != nil
}
func (model Model) GetAssertion(sec string, ptype string) (*Assertion, error) {
if model[sec] == nil {
return nil, fmt.Errorf("missing required section %s", sec)
}
if model[sec][ptype] == nil {
return nil, fmt.Errorf("missing required definition %s in section %s", ptype, sec)
}
return model[sec][ptype], nil
}
// PrintModel prints the model to the log.
func (model Model) PrintModel() {
// Logger has been removed - this is now a no-op
}
func (model Model) SortPoliciesBySubjectHierarchy() error {
if model["e"]["e"].Value != constant.SubjectPriorityEffect {
return nil
}
g, err := model.GetAssertion("g", "g")
if err != nil {
return err
}
subIndex := 0
for ptype, assertion := range model["p"] {
domainIndex, err := model.GetFieldIndex(ptype, constant.DomainIndex)
if err != nil {
domainIndex = -1
}
policies := assertion.Policy
subjectHierarchyMap, err := getSubjectHierarchyMap(g.Policy)
if err != nil {
return err
}
sort.SliceStable(policies, func(i, j int) bool {
domain1, domain2 := defaultDomain, defaultDomain
if domainIndex != -1 {
domain1 = policies[i][domainIndex]
domain2 = policies[j][domainIndex]
}
name1, name2 := getNameWithDomain(domain1, policies[i][subIndex]), getNameWithDomain(domain2, policies[j][subIndex])
p1 := subjectHierarchyMap[name1]
p2 := subjectHierarchyMap[name2]
return p1 > p2
})
for i, policy := range assertion.Policy {
assertion.PolicyMap[strings.Join(policy, ",")] = i
}
}
return nil
}
func getSubjectHierarchyMap(policies [][]string) (map[string]int, error) {
subjectHierarchyMap := make(map[string]int)
// Tree structure of role
policyMap := make(map[string][]string)
for _, policy := range policies {
if len(policy) < 2 {
return nil, errors.New("policy g expect 2 more params")
}
domain := defaultDomain
if len(policy) != 2 {
domain = policy[2]
}
child := getNameWithDomain(domain, policy[0])
parent := getNameWithDomain(domain, policy[1])
policyMap[parent] = append(policyMap[parent], child)
if _, ok := subjectHierarchyMap[child]; !ok {
subjectHierarchyMap[child] = 0
}
if _, ok := subjectHierarchyMap[parent]; !ok {
subjectHierarchyMap[parent] = 0
}
subjectHierarchyMap[child] = 1
}
// Use queues for levelOrder
queue := list.New()
for k, v := range subjectHierarchyMap {
root := k
if v != 0 {
continue
}
lv := 0
queue.PushBack(root)
for queue.Len() != 0 {
sz := queue.Len()
for i := 0; i < sz; i++ {
node := queue.Front()
queue.Remove(node)
nodeValue := node.Value.(string)
subjectHierarchyMap[nodeValue] = lv
if _, ok := policyMap[nodeValue]; ok {
for _, child := range policyMap[nodeValue] {
queue.PushBack(child)
}
}
}
lv++
}
}
return subjectHierarchyMap, nil
}
func getNameWithDomain(domain string, name string) string {
return domain + defaultSeparator + name
}
func (model Model) SortPoliciesByPriority() error {
for ptype, assertion := range model["p"] {
priorityIndex, err := model.GetFieldIndex(ptype, constant.PriorityIndex)
if err != nil {
continue
}
policies := assertion.Policy
sort.SliceStable(policies, func(i, j int) bool {
p1, err := strconv.Atoi(policies[i][priorityIndex])
if err != nil {
return true
}
p2, err := strconv.Atoi(policies[j][priorityIndex])
if err != nil {
return true
}
return p1 < p2
})
for i, policy := range assertion.Policy {
assertion.PolicyMap[strings.Join(policy, ",")] = i
}
}
return nil
}
var (
pPattern = regexp.MustCompile("^p_")
rPattern = regexp.MustCompile("^r_")
)
func (model Model) ToText() string {
tokenPatterns := make(map[string]string)
for _, ptype := range []string{"r", "p"} {
for _, token := range model[ptype][ptype].Tokens {
tokenPatterns[token] = rPattern.ReplaceAllString(pPattern.ReplaceAllString(token, "p."), "r.")
}
}
if strings.Contains(model["e"]["e"].Value, "p_eft") {
tokenPatterns["p_eft"] = "p.eft"
}
s := strings.Builder{}
writeString := func(sec string) {
for ptype := range model[sec] {
value := model[sec][ptype].Value
for tokenPattern, newToken := range tokenPatterns {
value = strings.Replace(value, tokenPattern, newToken, -1)
}
s.WriteString(fmt.Sprintf("%s = %s\n", sec, value))
}
}
s.WriteString("[request_definition]\n")
writeString("r")
s.WriteString("[policy_definition]\n")
writeString("p")
if _, ok := model["g"]; ok {
s.WriteString("[role_definition]\n")
for ptype := range model["g"] {
s.WriteString(fmt.Sprintf("%s = %s\n", ptype, model["g"][ptype].Value))
}
}
if _, ok := model["c"]; ok {
s.WriteString("[constraint_definition]\n")
for ptype := range model["c"] {
s.WriteString(fmt.Sprintf("%s = %s\n", ptype, model["c"][ptype].Value))
}
}
s.WriteString("[policy_effect]\n")
writeString("e")
s.WriteString("[matchers]\n")
writeString("m")
return s.String()
}
func (model Model) Copy() Model {
newModel := NewModel()
for sec, m := range model {
newAstMap := make(AssertionMap)
for ptype, ast := range m {
newAstMap[ptype] = ast.copy()
}
newModel[sec] = newAstMap
}
return newModel
}
func (model Model) GetFieldIndex(ptype string, field string) (int, error) {
assertion := model["p"][ptype]
assertion.FieldIndexMutex.RLock()
if index, ok := assertion.FieldIndexMap[field]; ok {
assertion.FieldIndexMutex.RUnlock()
return index, nil
}
assertion.FieldIndexMutex.RUnlock()
pattern := fmt.Sprintf("%s_"+field, ptype)
index := -1
for i, token := range assertion.Tokens {
if token == pattern {
index = i
break
}
}
if index == -1 {
return index, fmt.Errorf(field + " index is not set, please use enforcer.SetFieldIndex() to set index")
}
assertion.FieldIndexMutex.Lock()
assertion.FieldIndexMap[field] = index
assertion.FieldIndexMutex.Unlock()
return index, nil
}
================================================
FILE: model/model_test.go
================================================
// Copyright 2019 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/casbin/casbin/v3/config"
"github.com/casbin/casbin/v3/constant"
)
var (
basicExample = filepath.Join("..", "examples", "basic_model.conf")
basicConfig = &MockConfig{
data: map[string]string{
"request_definition::r": "sub, obj, act",
"policy_definition::p": "sub, obj, act",
"policy_effect::e": "some(where (p.eft == allow))",
"matchers::m": "r.sub == p.sub && r.obj == p.obj && r.act == p.act",
},
}
)
type MockConfig struct {
data map[string]string
config.ConfigInterface
}
func (mc *MockConfig) String(key string) string {
return mc.data[key]
}
func TestNewModel(t *testing.T) {
m := NewModel()
if m == nil {
t.Error("new model should not be nil")
}
}
func TestNewModelFromFile(t *testing.T) {
m, err := NewModelFromFile(basicExample)
if err != nil {
t.Errorf("model failed to load from file: %s", err)
}
if m == nil {
t.Error("model should not be nil")
}
}
func TestNewModelFromString(t *testing.T) {
modelBytes, _ := ioutil.ReadFile(basicExample)
modelString := string(modelBytes)
m, err := NewModelFromString(modelString)
if err != nil {
t.Errorf("model failed to load from string: %s", err)
}
if m == nil {
t.Error("model should not be nil")
}
}
func TestLoadModelFromConfig(t *testing.T) {
m := NewModel()
err := m.loadModelFromConfig(basicConfig)
if err != nil {
t.Error("basic config should not return an error")
}
m = NewModel()
err = m.loadModelFromConfig(&MockConfig{})
if err == nil {
t.Error("empty config should return error")
} else {
// check for missing sections in message
for _, rs := range requiredSections {
if !strings.Contains(err.Error(), sectionNameMap[rs]) {
t.Errorf("section name: %s should be in message", sectionNameMap[rs])
}
}
}
}
func TestHasSection(t *testing.T) {
m := NewModel()
_ = m.loadModelFromConfig(basicConfig)
for _, sec := range requiredSections {
if !m.hasSection(sec) {
t.Errorf("%s section was expected in model", sec)
}
}
m = NewModel()
_ = m.loadModelFromConfig(&MockConfig{})
for _, sec := range requiredSections {
if m.hasSection(sec) {
t.Errorf("%s section was not expected in model", sec)
}
}
}
func TestModel_AddDef(t *testing.T) {
m := NewModel()
s := "r"
v := "sub, obj, act"
ok := m.AddDef(s, s, v)
if !ok {
t.Errorf("non empty assertion should be added")
}
ok = m.AddDef(s, s, "")
if ok {
t.Errorf("empty assertion value should not be added")
}
}
func TestModelToTest(t *testing.T) {
testModelToText(t, "r.sub == p.sub && r.obj == p.obj && r_func(r.act, p.act) && testr_func(r.act, p.act)", "r_sub == p_sub && r_obj == p_obj && r_func(r_act, p_act) && testr_func(r_act, p_act)")
testModelToText(t, "r.sub == p.sub && r.obj == p.obj && p_func(r.act, p.act) && testp_func(r.act, p.act)", "r_sub == p_sub && r_obj == p_obj && p_func(r_act, p_act) && testp_func(r_act, p_act)")
}
func testModelToText(t *testing.T, mData, mExpected string) {
m := NewModel()
data := map[string]string{
"r": "sub, obj, act",
"p": "sub, obj, act",
"e": "some(where (p.eft == allow))",
"m": mData,
}
expected := map[string]string{
"r": "sub, obj, act",
"p": "sub, obj, act",
"e": constant.AllowOverrideEffect,
"m": mExpected,
}
addData := func(ptype string) {
m.AddDef(ptype, ptype, data[ptype])
}
for ptype := range data {
addData(ptype)
}
newM := NewModel()
print(m.ToText())
_ = newM.LoadModelFromText(m.ToText())
for ptype := range data {
if newM[ptype][ptype].Value != expected[ptype] {
t.Errorf("\"%s\" assertion value changed, current value: %s, it should be: %s", ptype, newM[ptype][ptype].Value, expected[ptype])
}
}
}
================================================
FILE: model/policy.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package model
import (
"fmt"
"strconv"
"strings"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
type (
PolicyOp int
)
const (
PolicyAdd PolicyOp = iota
PolicyRemove
)
const DefaultSep = ","
// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
func (model Model) BuildIncrementalRoleLinks(rmMap map[string]rbac.RoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
if sec == "g" && rmMap[ptype] != nil {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return err
}
return model[sec][ptype].buildIncrementalRoleLinks(rmMap[ptype], op, rules)
}
return nil
}
// BuildRoleLinks initializes the roles in RBAC.
func (model Model) BuildRoleLinks(rmMap map[string]rbac.RoleManager) error {
model.PrintPolicy()
for ptype, ast := range model["g"] {
if rm := rmMap[ptype]; rm != nil {
err := ast.buildRoleLinks(rm)
if err != nil {
return err
}
}
}
return nil
}
// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.
func (model Model) BuildIncrementalConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
if sec == "g" && condRmMap[ptype] != nil {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return err
}
return model[sec][ptype].buildIncrementalConditionalRoleLinks(condRmMap[ptype], op, rules)
}
return nil
}
// BuildConditionalRoleLinks initializes the roles in RBAC.
func (model Model) BuildConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager) error {
model.PrintPolicy()
for ptype, ast := range model["g"] {
if condRm := condRmMap[ptype]; condRm != nil {
err := ast.buildConditionalRoleLinks(condRm)
if err != nil {
return err
}
}
}
return nil
}
// PrintPolicy prints the policy to log.
func (model Model) PrintPolicy() {
// Logger has been removed - this is now a no-op
}
// ClearPolicy clears all current policy.
func (model Model) ClearPolicy() {
for _, ast := range model["p"] {
ast.Policy = nil
ast.PolicyMap = map[string]int{}
}
for _, ast := range model["g"] {
ast.Policy = nil
ast.PolicyMap = map[string]int{}
}
}
// GetPolicy gets all rules in a policy.
func (model Model) GetPolicy(sec string, ptype string) ([][]string, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return nil, err
}
return model[sec][ptype].Policy, nil
}
// GetFilteredPolicy gets rules based on field filters from a policy.
func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return nil, err
}
res := [][]string{}
for _, rule := range model[sec][ptype].Policy {
matched := true
for i, fieldValue := range fieldValues {
if fieldValue != "" && rule[fieldIndex+i] != fieldValue {
matched = false
break
}
}
if matched {
res = append(res, rule)
}
}
return res, nil
}
// HasPolicyEx determines whether a model has the specified policy rule with error.
func (model Model) HasPolicyEx(sec string, ptype string, rule []string) (bool, error) {
assertion, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, err
}
switch sec {
case "p":
if len(rule) != len(assertion.Tokens) {
return false, fmt.Errorf(
"invalid policy rule size: expected %d, got %d, rule: %v",
len(model["p"][ptype].Tokens),
len(rule),
rule)
}
case "g":
if len(rule) < len(assertion.Tokens) {
return false, fmt.Errorf(
"invalid policy rule size: expected %d, got %d, rule: %v",
len(model["g"][ptype].Tokens),
len(rule),
rule)
}
}
return model.HasPolicy(sec, ptype, rule)
}
// HasPolicy determines whether a model has the specified policy rule.
func (model Model) HasPolicy(sec string, ptype string, rule []string) (bool, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, err
}
_, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
return ok, nil
}
// HasPolicies determines whether a model has any of the specified policies. If one is found we return true.
func (model Model) HasPolicies(sec string, ptype string, rules [][]string) (bool, error) {
for i := 0; i < len(rules); i++ {
ok, err := model.HasPolicy(sec, ptype, rules[i])
if err != nil {
return false, err
}
if ok {
return true, nil
}
}
return false, nil
}
// AddPolicy adds a policy rule to the model.
func (model Model) AddPolicy(sec string, ptype string, rule []string) error {
assertion, err := model.GetAssertion(sec, ptype)
if err != nil {
return err
}
assertion.Policy = append(assertion.Policy, rule)
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1
hasPriority := false
if _, ok := assertion.FieldIndexMap[constant.PriorityIndex]; ok {
hasPriority = true
}
if sec == "p" && hasPriority {
if idxInsert, err := strconv.Atoi(rule[assertion.FieldIndexMap[constant.PriorityIndex]]); err == nil {
i := len(assertion.Policy) - 1
for ; i > 0; i-- {
idx, err := strconv.Atoi(assertion.Policy[i-1][assertion.FieldIndexMap[constant.PriorityIndex]])
if err != nil || idx <= idxInsert {
break
}
assertion.Policy[i] = assertion.Policy[i-1]
assertion.PolicyMap[strings.Join(assertion.Policy[i-1], DefaultSep)]++
}
assertion.Policy[i] = rule
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = i
}
}
return nil
}
// AddPolicies adds policy rules to the model.
func (model Model) AddPolicies(sec string, ptype string, rules [][]string) error {
_, err := model.AddPoliciesWithAffected(sec, ptype, rules)
return err
}
// AddPoliciesWithAffected adds policy rules to the model, and returns affected rules.
func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return nil, err
}
var affected [][]string
for _, rule := range rules {
hashKey := strings.Join(rule, DefaultSep)
_, ok := model[sec][ptype].PolicyMap[hashKey]
if ok {
continue
}
affected = append(affected, rule)
err = model.AddPolicy(sec, ptype, rule)
if err != nil {
return affected, err
}
}
return affected, err
}
// RemovePolicy removes a policy rule from the model.
// Deprecated: Using AddPoliciesWithAffected instead.
func (model Model) RemovePolicy(sec string, ptype string, rule []string) (bool, error) {
ast, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, err
}
key := strings.Join(rule, DefaultSep)
index, ok := ast.PolicyMap[key]
if !ok {
return false, nil
}
lastIdx := len(ast.Policy) - 1
if index != lastIdx {
ast.Policy[index] = ast.Policy[lastIdx]
lastPolicyKey := strings.Join(ast.Policy[index], DefaultSep)
ast.PolicyMap[lastPolicyKey] = index
}
ast.Policy = ast.Policy[:lastIdx]
delete(ast.PolicyMap, key)
return true, nil
}
// UpdatePolicy updates a policy rule from the model.
func (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, err
}
oldPolicy := strings.Join(oldRule, DefaultSep)
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
if !ok {
return false, nil
}
model[sec][ptype].Policy[index] = newRule
delete(model[sec][ptype].PolicyMap, oldPolicy)
model[sec][ptype].PolicyMap[strings.Join(newRule, DefaultSep)] = index
return true, nil
}
// UpdatePolicies updates a policy rule from the model.
func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, err
}
rollbackFlag := false
// index -> []{oldIndex, newIndex}
modifiedRuleIndex := make(map[int][]int)
// rollback
defer func() {
if rollbackFlag {
for index, oldNewIndex := range modifiedRuleIndex {
model[sec][ptype].Policy[index] = oldRules[oldNewIndex[0]]
oldPolicy := strings.Join(oldRules[oldNewIndex[0]], DefaultSep)
newPolicy := strings.Join(newRules[oldNewIndex[1]], DefaultSep)
delete(model[sec][ptype].PolicyMap, newPolicy)
model[sec][ptype].PolicyMap[oldPolicy] = index
}
}
}()
newIndex := 0
for oldIndex, oldRule := range oldRules {
oldPolicy := strings.Join(oldRule, DefaultSep)
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
if !ok {
rollbackFlag = true
return false, nil
}
model[sec][ptype].Policy[index] = newRules[newIndex]
delete(model[sec][ptype].PolicyMap, oldPolicy)
model[sec][ptype].PolicyMap[strings.Join(newRules[newIndex], DefaultSep)] = index
modifiedRuleIndex[index] = []int{oldIndex, newIndex}
newIndex++
}
return true, nil
}
// RemovePolicies removes policy rules from the model.
func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {
affected, err := model.RemovePoliciesWithAffected(sec, ptype, rules)
return len(affected) != 0, err
}
// RemovePoliciesWithAffected removes policy rules from the model, and returns affected rules.
func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return nil, err
}
var affected [][]string
for _, rule := range rules {
index, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
if !ok {
continue
}
affected = append(affected, rule)
model[sec][ptype].Policy = append(model[sec][ptype].Policy[:index], model[sec][ptype].Policy[index+1:]...)
delete(model[sec][ptype].PolicyMap, strings.Join(rule, DefaultSep))
for i := index; i < len(model[sec][ptype].Policy); i++ {
model[sec][ptype].PolicyMap[strings.Join(model[sec][ptype].Policy[i], DefaultSep)] = i
}
}
return affected, nil
}
// RemoveFilteredPolicy removes policy rules based on field filters from the model.
func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string, error) {
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return false, nil, err
}
var tmp [][]string
var effects [][]string
res := false
model[sec][ptype].PolicyMap = map[string]int{}
for _, rule := range model[sec][ptype].Policy {
matched := true
for i, fieldValue := range fieldValues {
if fieldValue != "" && rule[fieldIndex+i] != fieldValue {
matched = false
break
}
}
if matched {
effects = append(effects, rule)
} else {
tmp = append(tmp, rule)
model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(tmp) - 1
}
}
if len(tmp) != len(model[sec][ptype].Policy) {
model[sec][ptype].Policy = tmp
res = true
}
return res, effects, nil
}
// GetValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed.
func (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) ([]string, error) {
values := []string{}
_, err := model.GetAssertion(sec, ptype)
if err != nil {
return nil, err
}
for _, rule := range model[sec][ptype].Policy {
values = append(values, rule[fieldIndex])
}
util.ArrayRemoveDuplicates(&values)
return values, nil
}
// GetValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
func (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) ([]string, error) {
values := []string{}
for ptype := range model[sec] {
v, err := model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)
if err != nil {
return nil, err
}
values = append(values, v...)
}
util.ArrayRemoveDuplicates(&values)
return values, nil
}
// GetValuesForFieldInPolicyAllTypesByName gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
func (model Model) GetValuesForFieldInPolicyAllTypesByName(sec string, field string) ([]string, error) {
values := []string{}
for ptype := range model[sec] {
// GetFieldIndex will return (-1, err) if field is not found, ignore it
index, err := model.GetFieldIndex(ptype, field)
if err != nil {
continue
}
v, err := model.GetValuesForFieldInPolicy(sec, ptype, index)
if err != nil {
return nil, err
}
values = append(values, v...)
}
util.ArrayRemoveDuplicates(&values)
return values, nil
}
================================================
FILE: model_b_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"testing"
"github.com/casbin/casbin/v3/util"
)
func rawEnforce(sub string, obj string, act string) bool {
policy := [2][3]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}}
for _, rule := range policy {
if sub == rule[0] && obj == rule[1] && act == rule[2] {
return true
}
}
return false
}
func BenchmarkRaw(b *testing.B) {
for i := 0; i < b.N; i++ {
rawEnforce("alice", "data1", "read")
}
}
func BenchmarkBasicModel(b *testing.B) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkRBACModel(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data2", "read")
}
}
func BenchmarkRBACModelSizes(b *testing.B) {
cases := []struct {
name string
roles int
resources int
users int
}{
{name: "small", roles: 100, resources: 10, users: 1000},
{name: "medium", roles: 1000, resources: 100, users: 10000},
{name: "large", roles: 10000, resources: 1000, users: 100000},
}
for _, c := range cases {
c := c
e, err := NewEnforcer("examples/rbac_model.conf")
if err != nil {
b.Fatal(err)
}
pPolicies := make([][]string, c.roles)
for i := range pPolicies {
pPolicies[i] = []string{
fmt.Sprintf("group-has-a-very-long-name-%d", i),
fmt.Sprintf("data-has-a-very-long-name-%d", i%c.resources),
"read",
}
}
if _, err := e.AddPolicies(pPolicies); err != nil {
b.Fatal(err)
}
gPolicies := make([][]string, c.users)
for i := range gPolicies {
gPolicies[i] = []string{
fmt.Sprintf("user-has-a-very-long-name-%d", i),
fmt.Sprintf("group-has-a-very-long-name-%d", i%c.roles),
}
}
if _, err := e.AddGroupingPolicies(gPolicies); err != nil {
b.Fatal(err)
}
// Set up enforcements, alternating between things a user can access
// and things they cannot. Use 17 tests so that we get a variety of users
// and roles rather than always landing on a multiple of 2/10/whatever.
enforcements := make([][]interface{}, 17)
for i := range enforcements {
userNum := (c.users / len(enforcements)) * i
roleNum := userNum % c.roles
resourceNum := roleNum % c.resources
if i%2 == 0 {
resourceNum += 1
resourceNum %= c.resources
}
enforcements[i] = []interface{}{
fmt.Sprintf("user-has-a-very-long-name-%d", userNum),
fmt.Sprintf("data-has-a-very-long-name-%d", resourceNum),
"read",
}
}
b.Run(c.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = e.Enforce(enforcements[i%len(enforcements)]...)
}
})
}
}
func BenchmarkRBACModelSmall(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// 100 roles, 10 resources.
for i := 0; i < 100; i++ {
_, err := e.AddPolicy(fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read")
if err != nil {
b.Fatal(err)
}
}
// 1000 users.
for i := 0; i < 1000; i++ {
_, err := e.AddGroupingPolicy(fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10))
if err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user501", "data9", "read")
}
}
func BenchmarkRBACModelMedium(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 10000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user5001", "data99", "read")
}
}
func BenchmarkRBACModelLarge(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 100000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 100000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("user50001", "data999", "read")
}
}
func BenchmarkRBACModelWithResourceRoles(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_with_resource_roles_model.conf", "examples/rbac_with_resource_roles_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkRBACModelWithDomains(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "domain1", "data1", "read")
}
}
func BenchmarkABACModel(b *testing.B) {
e, _ := NewEnforcer("examples/abac_model.conf")
data1 := newTestResource("data1", "alice")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", data1, "read")
}
}
func BenchmarkABACRuleModel(b *testing.B) {
e, _ := NewEnforcer("examples/abac_rule_model.conf")
sub := newTestSubject("alice", 18)
for i := 0; i < 1000; i++ {
_, _ = e.AddPolicy("r.sub.Age > 20", fmt.Sprintf("data%d", i), "read")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce(sub, "data100", "read")
}
}
func BenchmarkKeyMatchModel(b *testing.B) {
e, _ := NewEnforcer("examples/keymatch_model.conf", "examples/keymatch_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "/alice_data/resource1", "GET")
}
}
func BenchmarkRBACModelWithDeny(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_with_deny_model.conf", "examples/rbac_with_deny_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkPriorityModel(b *testing.B) {
e, _ := NewEnforcer("examples/priority_model.conf", "examples/priority_policy.csv")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("alice", "data1", "read")
}
}
func BenchmarkRBACModelWithDomainPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
_ = e.BuildRoleLinks()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = e.Enforce("staffUser1001", "/orgs/1/sites/site001", "App001.Module001.Action1001")
}
}
================================================
FILE: model_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
"github.com/casbin/casbin/v3/model"
fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
func testEnforce(t *testing.T, e *Enforcer, sub interface{}, obj interface{}, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, obj, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, %v, %s: %t, supposed to be %t", sub, obj, act, myRes, res)
}
}
func testEnforceWithoutUsers(t *testing.T, e *Enforcer, obj string, act string, res bool) {
t.Helper()
if myRes, _ := e.Enforce(obj, act); myRes != res {
t.Errorf("%s, %s: %t, supposed to be %t", obj, act, myRes, res)
}
}
func testDomainEnforce(t *testing.T, e *Enforcer, sub string, dom string, obj string, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, dom, obj, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, %s, %s, %s: %t, supposed to be %t", sub, dom, obj, act, myRes, res)
}
}
func TestBasicModel(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestBasicModelWithoutSpaces(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model_without_spaces.conf", "examples/basic_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestBasicModelNoPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/basic_model.conf")
testEnforce(t, e, "alice", "data1", "read", false)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", false)
}
func TestBasicModelWithRoot(t *testing.T) {
e, _ := NewEnforcer("examples/basic_with_root_model.conf", "examples/basic_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
testEnforce(t, e, "root", "data1", "read", true)
testEnforce(t, e, "root", "data1", "write", true)
testEnforce(t, e, "root", "data2", "read", true)
testEnforce(t, e, "root", "data2", "write", true)
}
func TestBasicModelWithRootNoPolicy(t *testing.T) {
e, _ := NewEnforcer("examples/basic_with_root_model.conf")
testEnforce(t, e, "alice", "data1", "read", false)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", false)
testEnforce(t, e, "root", "data1", "read", true)
testEnforce(t, e, "root", "data1", "write", true)
testEnforce(t, e, "root", "data2", "read", true)
testEnforce(t, e, "root", "data2", "write", true)
}
func TestBasicModelWithoutUsers(t *testing.T) {
e, _ := NewEnforcer("examples/basic_without_users_model.conf", "examples/basic_without_users_policy.csv")
testEnforceWithoutUsers(t, e, "data1", "read", true)
testEnforceWithoutUsers(t, e, "data1", "write", false)
testEnforceWithoutUsers(t, e, "data2", "read", false)
testEnforceWithoutUsers(t, e, "data2", "write", true)
}
func TestBasicModelWithoutResources(t *testing.T) {
e, _ := NewEnforcer("examples/basic_without_resources_model.conf", "examples/basic_without_resources_policy.csv")
testEnforceWithoutUsers(t, e, "alice", "read", true)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", false)
testEnforceWithoutUsers(t, e, "bob", "write", true)
}
func TestRBACModel(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRBACModelWithResourceRoles(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_resource_roles_model.conf", "examples/rbac_with_resource_roles_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", true)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRBACModelWithDomains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
}
func TestRBACModelWithDomainsAtRuntime(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf")
_, _ = e.AddPolicy("admin", "domain1", "data1", "read")
_, _ = e.AddPolicy("admin", "domain1", "data1", "write")
_, _ = e.AddPolicy("admin", "domain2", "data2", "read")
_, _ = e.AddPolicy("admin", "domain2", "data2", "write")
_, _ = e.AddGroupingPolicy("alice", "admin", "domain1")
_, _ = e.AddGroupingPolicy("bob", "admin", "domain2")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
// Remove all policy rules related to domain1 and data1.
_, _ = e.RemoveFilteredPolicy(1, "domain1", "data1")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
// Remove the specified policy rule.
_, _ = e.RemovePolicy("admin", "domain2", "data2", "read")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
}
func TestRBACModelWithDomainsAtRuntimeMockAdapter(t *testing.T) {
adapter := fileadapter.NewAdapterMock("examples/rbac_with_domains_policy.csv")
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", adapter)
_, _ = e.AddPolicy("admin", "domain3", "data1", "read")
_, _ = e.AddGroupingPolicy("alice", "admin", "domain3")
testDomainEnforce(t, e, "alice", "domain3", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
_, _ = e.RemoveFilteredPolicy(1, "domain1", "data1")
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
_, _ = e.RemovePolicy("admin", "domain2", "data2", "read")
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", false)
}
func TestRBACModelWithDomainTokenRename(t *testing.T) {
// Test that renaming the domain token from "dom" to another name (e.g., "dom1")
// still works correctly. This is a regression test for the issue where the
// hardcoded "r_dom" and "p_dom" strings prevented proper domain matching.
// Test with standard "dom" token
modelText1 := `
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.act
`
m1, _ := model.NewModelFromString(modelText1)
e1, _ := NewEnforcer(m1)
_, _ = e1.AddPolicy("admin", "domain1", "data1", "read")
_, _ = e1.AddGroupingPolicy("alice", "admin", "domain*")
testDomainEnforce(t, e1, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e1, "alice", "domain2", "data1", "read", false)
// Test with renamed "dom1" token
modelText2 := `
[request_definition]
r = sub, dom1, obj, act
[policy_definition]
p = sub, dom1, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom1) && keyMatch(r.dom1, p.dom1) && r.obj == p.obj && r.act == p.act
`
m2, _ := model.NewModelFromString(modelText2)
e2, _ := NewEnforcer(m2)
_, _ = e2.AddPolicy("admin", "domain1", "data1", "read")
_, _ = e2.AddGroupingPolicy("alice", "admin", "domain*")
testDomainEnforce(t, e2, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e2, "alice", "domain2", "data1", "read", false)
// Test with renamed "tenant" token
modelText3 := `
[request_definition]
r = sub, tenant, obj, act
[policy_definition]
p = sub, tenant, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.tenant) && keyMatch(r.tenant, p.tenant) && r.obj == p.obj && r.act == p.act
`
m3, _ := model.NewModelFromString(modelText3)
e3, _ := NewEnforcer(m3)
_, _ = e3.AddPolicy("admin", "domain1", "data1", "read")
_, _ = e3.AddGroupingPolicy("alice", "admin", "domain*")
testDomainEnforce(t, e3, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e3, "alice", "domain2", "data1", "read", false)
}
func TestRBACModelWithDeny(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_deny_model.conf", "examples/rbac_with_deny_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRBACModelWithOnlyDeny(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_not_deny_model.conf", "examples/rbac_with_deny_policy.csv")
testEnforce(t, e, "alice", "data2", "write", false)
}
func TestRBACModelWithCustomData(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
// You can add custom data to a grouping policy, Casbin will ignore it. It is only meaningful to the caller.
// This feature can be used to store information like whether "bob" is an end user (so no subject will inherit "bob")
// For Casbin, it is equivalent to: e.AddGroupingPolicy("bob", "data2_admin")
_, _ = e.AddGroupingPolicy("bob", "data2_admin", "custom_data")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", true)
testEnforce(t, e, "bob", "data2", "write", true)
// You should also take the custom data as a parameter when deleting a grouping policy.
// e.RemoveGroupingPolicy("bob", "data2_admin") won't work.
// Or you can remove it by using RemoveFilteredGroupingPolicy().
_, _ = e.RemoveGroupingPolicy("bob", "data2_admin", "custom_data")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRBACModelWithPattern(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
// Here's a little confusing: the matching function here is not the custom function used in matcher.
// It is the matching function used by "g" (and "g2", "g3" if any..)
// You can see in policy that: "g2, /book/:id, book_group", so in "g2()" function in the matcher, instead
// of checking whether "/book/:id" equals the obj: "/book/1", it checks whether the pattern matches.
// You can see it as normal RBAC: "/book/:id" == "/book/1" becomes KeyMatch2("/book/:id", "/book/1")
e.AddNamedMatchingFunc("g2", "KeyMatch2", util.KeyMatch2)
e.AddNamedMatchingFunc("g", "KeyMatch2", util.KeyMatch2)
testEnforce(t, e, "any_user", "/pen3/1", "GET", true)
testEnforce(t, e, "/book/user/1", "/pen4/1", "GET", true)
testEnforce(t, e, "/book/user/1", "/pen4/1", "POST", true)
testEnforce(t, e, "alice", "/book/1", "GET", true)
testEnforce(t, e, "alice", "/book/2", "GET", true)
testEnforce(t, e, "alice", "/pen/1", "GET", true)
testEnforce(t, e, "alice", "/pen/2", "GET", false)
testEnforce(t, e, "bob", "/book/1", "GET", false)
testEnforce(t, e, "bob", "/book/2", "GET", false)
testEnforce(t, e, "bob", "/pen/1", "GET", true)
testEnforce(t, e, "bob", "/pen/2", "GET", true)
// AddMatchingFunc() is actually setting a function because only one function is allowed,
// so when we set "KeyMatch3", we are actually replacing "KeyMatch2" with "KeyMatch3".
e.AddNamedMatchingFunc("g2", "KeyMatch2", util.KeyMatch3)
testEnforce(t, e, "alice", "/book2/1", "GET", true)
testEnforce(t, e, "alice", "/book2/2", "GET", true)
testEnforce(t, e, "alice", "/pen2/1", "GET", true)
testEnforce(t, e, "alice", "/pen2/2", "GET", false)
testEnforce(t, e, "bob", "/book2/1", "GET", false)
testEnforce(t, e, "bob", "/book2/2", "GET", false)
testEnforce(t, e, "bob", "/pen2/1", "GET", true)
testEnforce(t, e, "bob", "/pen2/2", "GET", true)
}
func TestRBACModelWithDifferentTypesOfRoles(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_different_types_of_roles_model.conf", "examples/rbac_with_different_types_of_roles_policy.csv")
g, err := e.GetNamedGroupingPolicy("g")
if err != nil {
t.Error(err)
}
for _, gp := range g {
if len(gp) != 5 {
t.Error("g parameters' num isn't 5")
return
}
e.AddNamedDomainLinkConditionFunc("g", gp[0], gp[1], gp[2], util.TimeMatchFunc)
}
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", true)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", true)
testEnforce(t, e, "bob", "data2", "write", false)
testEnforce(t, e, "carol", "data1", "read", false)
testEnforce(t, e, "carol", "data1", "write", false)
testEnforce(t, e, "carol", "data2", "read", false)
testEnforce(t, e, "carol", "data2", "write", false)
}
type testCustomRoleManager struct{}
func NewRoleManager() rbac.RoleManager {
return &testCustomRoleManager{}
}
func (rm *testCustomRoleManager) Clear() error { return nil }
func (rm *testCustomRoleManager) AddLink(name1 string, name2 string, domain ...string) error {
return nil
}
func (rm *testCustomRoleManager) BuildRelationship(name1 string, name2 string, domain ...string) error {
return nil
}
func (rm *testCustomRoleManager) DeleteLink(name1 string, name2 string, domain ...string) error {
return nil
}
func (rm *testCustomRoleManager) HasLink(name1 string, name2 string, domain ...string) (bool, error) {
if name1 == "alice" && name2 == "alice" {
return true, nil
} else if name1 == "alice" && name2 == "data2_admin" {
return true, nil
} else if name1 == "bob" && name2 == "bob" {
return true, nil
}
return false, nil
}
func (rm *testCustomRoleManager) GetRoles(name string, domain ...string) ([]string, error) {
return []string{}, nil
}
func (rm *testCustomRoleManager) GetUsers(name string, domain ...string) ([]string, error) {
return []string{}, nil
}
func (rm *testCustomRoleManager) GetDomains(name string) ([]string, error) {
return []string{}, nil
}
func (rm *testCustomRoleManager) GetAllDomains() ([]string, error) {
return []string{}, nil
}
func (rm *testCustomRoleManager) PrintRoles() error { return nil }
func (rm *testCustomRoleManager) Match(str string, pattern string) bool { return true }
func (rm *testCustomRoleManager) AddMatchingFunc(name string, fn rbac.MatchingFunc) {}
func (rm *testCustomRoleManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {}
func (rm *testCustomRoleManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {
}
func (rm *testCustomRoleManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {
}
func (rm *testCustomRoleManager) AddDomainLinkConditionFunc(user string, role string, domain string, fn rbac.LinkConditionFunc) {
}
func (rm *testCustomRoleManager) SetDomainLinkConditionFuncParams(user string, role string, domain string, params ...string) {
}
func (rm *testCustomRoleManager) DeleteDomain(domain string) error {
return nil
}
func (rm *testCustomRoleManager) GetImplicitRoles(name string, domain ...string) ([]string, error) {
return []string{}, nil
}
func (rm *testCustomRoleManager) GetImplicitUsers(name string, domain ...string) ([]string, error) {
return []string{}, nil
}
func TestRBACModelWithCustomRoleManager(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
e.SetRoleManager(NewRoleManager())
_ = e.LoadModel()
_ = e.LoadPolicy()
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestKeyMatchModel(t *testing.T) {
e, _ := NewEnforcer("examples/keymatch_model.conf", "examples/keymatch_policy.csv")
testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource1", "POST", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "alice", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource1", "POST", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "alice", "/bob_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/alice_data/resource1", "POST", false)
testEnforce(t, e, "bob", "/alice_data/resource2", "GET", true)
testEnforce(t, e, "bob", "/alice_data/resource2", "POST", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource1", "POST", true)
testEnforce(t, e, "bob", "/bob_data/resource2", "GET", false)
testEnforce(t, e, "bob", "/bob_data/resource2", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "GET", true)
testEnforce(t, e, "cathy", "/cathy_data", "POST", true)
testEnforce(t, e, "cathy", "/cathy_data", "DELETE", false)
}
func TestKeyMatch2Model(t *testing.T) {
e, _ := NewEnforcer("examples/keymatch2_model.conf", "examples/keymatch2_policy.csv")
testEnforce(t, e, "alice", "/alice_data", "GET", false)
testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
testEnforce(t, e, "alice", "/alice_data2/myid", "GET", false)
testEnforce(t, e, "alice", "/alice_data2/myid/using/res_id", "GET", true)
}
func CustomFunction(key1 string, key2 string) bool {
if key1 == "/alice_data2/myid/using/res_id" && key2 == "/alice_data/:resource" {
return true
} else if key1 == "/alice_data2/myid/using/res_id" && key2 == "/alice_data2/:id/using/:resId" {
return true
} else {
return false
}
}
func CustomFunctionWrapper(args ...interface{}) (interface{}, error) {
key1 := args[0].(string)
key2 := args[1].(string)
return CustomFunction(key1, key2), nil
}
func TestKeyMatchCustomModel(t *testing.T) {
e, _ := NewEnforcer("examples/keymatch_custom_model.conf", "examples/keymatch2_policy.csv")
e.AddFunction("keyMatchCustom", CustomFunctionWrapper)
testEnforce(t, e, "alice", "/alice_data2/myid", "GET", false)
testEnforce(t, e, "alice", "/alice_data2/myid/using/res_id", "GET", true)
}
func TestIPMatchModel(t *testing.T) {
e, _ := NewEnforcer("examples/ipmatch_model.conf", "examples/ipmatch_policy.csv")
testEnforce(t, e, "192.168.2.123", "data1", "read", true)
testEnforce(t, e, "192.168.2.123", "data1", "write", false)
testEnforce(t, e, "192.168.2.123", "data2", "read", false)
testEnforce(t, e, "192.168.2.123", "data2", "write", false)
testEnforce(t, e, "192.168.0.123", "data1", "read", false)
testEnforce(t, e, "192.168.0.123", "data1", "write", false)
testEnforce(t, e, "192.168.0.123", "data2", "read", false)
testEnforce(t, e, "192.168.0.123", "data2", "write", false)
testEnforce(t, e, "10.0.0.5", "data1", "read", false)
testEnforce(t, e, "10.0.0.5", "data1", "write", false)
testEnforce(t, e, "10.0.0.5", "data2", "read", false)
testEnforce(t, e, "10.0.0.5", "data2", "write", true)
testEnforce(t, e, "192.168.0.1", "data1", "read", false)
testEnforce(t, e, "192.168.0.1", "data1", "write", false)
testEnforce(t, e, "192.168.0.1", "data2", "read", false)
testEnforce(t, e, "192.168.0.1", "data2", "write", false)
}
func TestGlobMatchModel(t *testing.T) {
e, _ := NewEnforcer("examples/glob_model.conf", "examples/glob_policy.csv")
testEnforce(t, e, "u1", "/foo/", "read", true)
testEnforce(t, e, "u1", "/foo", "read", false)
testEnforce(t, e, "u1", "/foo/subprefix", "read", true)
testEnforce(t, e, "u1", "foo", "read", false)
testEnforce(t, e, "u2", "/foosubprefix", "read", true)
testEnforce(t, e, "u2", "/foo/subprefix", "read", false)
testEnforce(t, e, "u2", "foo", "read", false)
testEnforce(t, e, "u3", "/prefix/foo/subprefix", "read", true)
testEnforce(t, e, "u3", "/prefix/foo/", "read", true)
testEnforce(t, e, "u3", "/prefix/foo", "read", false)
testEnforce(t, e, "u4", "/foo", "read", false)
testEnforce(t, e, "u4", "foo", "read", true)
}
func TestPriorityModel(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model.conf", "examples/priority_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", true)
testEnforce(t, e, "bob", "data2", "write", false)
}
func TestPriorityModelIndeterminate(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model.conf", "examples/priority_indeterminate_policy.csv")
testEnforce(t, e, "alice", "data1", "read", false)
}
func TestRBACModelInMultiLines(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model_in_multi_line.conf", "examples/rbac_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestCommentModel(t *testing.T) {
e, _ := NewEnforcer("examples/comment_model.conf", "examples/basic_policy.csv")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestDomainMatchModel(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
e.AddNamedDomainMatchingFunc("g", "keyMatch2", util.KeyMatch2)
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
testDomainEnforce(t, e, "alice", "domain1", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain1", "data2", "write", false)
testDomainEnforce(t, e, "alice", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "alice", "domain2", "data2", "write", true)
testDomainEnforce(t, e, "bob", "domain2", "data1", "read", false)
testDomainEnforce(t, e, "bob", "domain2", "data1", "write", false)
testDomainEnforce(t, e, "bob", "domain2", "data2", "read", true)
testDomainEnforce(t, e, "bob", "domain2", "data2", "write", true)
}
func TestAllMatchModel(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_all_pattern_model.conf", "examples/rbac_with_all_pattern_policy.csv")
e.AddNamedMatchingFunc("g", "keyMatch2", util.KeyMatch2)
e.AddNamedDomainMatchingFunc("g", "keyMatch2", util.KeyMatch2)
testDomainEnforce(t, e, "alice", "domain1", "/book/1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "/book/1", "write", false)
testDomainEnforce(t, e, "alice", "domain2", "/book/1", "read", false)
testDomainEnforce(t, e, "alice", "domain2", "/book/1", "write", true)
}
func TestTemporalRolesModel(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_temporal_roles_model.conf", "examples/rbac_with_temporal_roles_policy.csv")
e.AddNamedLinkConditionFunc("g", "alice", "data2_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data3_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data4_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data5_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data6_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data7_admin", util.TimeMatchFunc)
e.AddNamedLinkConditionFunc("g", "alice", "data8_admin", util.TimeMatchFunc)
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data1", "write", true)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "alice", "data3", "read", true)
testEnforce(t, e, "alice", "data3", "write", true)
testEnforce(t, e, "alice", "data4", "read", true)
testEnforce(t, e, "alice", "data4", "write", true)
testEnforce(t, e, "alice", "data5", "read", true)
testEnforce(t, e, "alice", "data5", "write", true)
testEnforce(t, e, "alice", "data6", "read", false)
testEnforce(t, e, "alice", "data6", "write", false)
testEnforce(t, e, "alice", "data7", "read", true)
testEnforce(t, e, "alice", "data7", "write", true)
testEnforce(t, e, "alice", "data8", "read", false)
testEnforce(t, e, "alice", "data8", "write", false)
}
func TestTemporalRolesModelWithDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domain_temporal_roles_model.conf", "examples/rbac_with_domain_temporal_roles_policy.csv")
e.AddNamedDomainLinkConditionFunc("g", "alice", "data2_admin", "domain2", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data3_admin", "domain3", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data4_admin", "domain4", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data5_admin", "domain5", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data6_admin", "domain6", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data7_admin", "domain7", util.TimeMatchFunc)
e.AddNamedDomainLinkConditionFunc("g", "alice", "data8_admin", "domain8", util.TimeMatchFunc)
testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
testDomainEnforce(t, e, "alice", "domain2", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain2", "data2", "write", false)
testDomainEnforce(t, e, "alice", "domain3", "data3", "read", true)
testDomainEnforce(t, e, "alice", "domain3", "data3", "write", true)
testDomainEnforce(t, e, "alice", "domain4", "data4", "read", true)
testDomainEnforce(t, e, "alice", "domain4", "data4", "write", true)
testDomainEnforce(t, e, "alice", "domain5", "data5", "read", true)
testDomainEnforce(t, e, "alice", "domain5", "data5", "write", true)
testDomainEnforce(t, e, "alice", "domain6", "data6", "read", false)
testDomainEnforce(t, e, "alice", "domain6", "data6", "write", false)
testDomainEnforce(t, e, "alice", "domain7", "data7", "read", true)
testDomainEnforce(t, e, "alice", "domain7", "data7", "write", true)
testDomainEnforce(t, e, "alice", "domain8", "data8", "read", false)
testDomainEnforce(t, e, "alice", "domain8", "data8", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data1", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data1", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data2", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data2", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data3", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data3", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data4", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data4", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data5", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data5", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data6", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data6", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data7", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data7", "write", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data8", "read", false)
testDomainEnforce(t, e, "alice", "domain_not_exist", "data8", "write", false)
}
func TestReBACModel(t *testing.T) {
e, _ := NewEnforcer("examples/rebac_model.conf", "examples/rebac_policy.csv")
testEnforce(t, e, "alice", "doc1", "read", true)
testEnforce(t, e, "alice", "doc1", "write", false)
testEnforce(t, e, "alice", "doc2", "read", false)
testEnforce(t, e, "alice", "doc2", "write", false)
testEnforce(t, e, "alice", "doc3", "read", false)
testEnforce(t, e, "alice", "doc3", "write", false)
testEnforce(t, e, "bob", "doc1", "read", false)
testEnforce(t, e, "bob", "doc1", "write", false)
testEnforce(t, e, "bob", "doc2", "read", true)
testEnforce(t, e, "bob", "doc2", "write", false)
testEnforce(t, e, "bob", "doc3", "read", false)
testEnforce(t, e, "bob", "doc3", "write", false)
}
================================================
FILE: orbac_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
// TestOrBACModel tests the Organization-Based Access Control (OrBAC) model.
// OrBAC extends RBAC with abstraction layers:
// - Empower (g): Maps subjects to roles within organizations
// - Use (g2): Maps concrete actions to abstract activities within organizations
// - Consider (g3): Maps concrete objects to abstract views within organizations
// - Permission (p): Grants role-activity-view permissions within organizations
//
// This separates concrete entities (subjects, actions, objects) from
// abstract security entities (roles, activities, views), allowing more
// flexible and maintainable access control policies.
func testEnforceOrBAC(t *testing.T, e *Enforcer, sub string, org string, obj string, act string, res bool) {
t.Helper()
if myRes, err := e.Enforce(sub, org, obj, act); err != nil {
t.Errorf("Enforce Error: %s", err)
} else if myRes != res {
t.Errorf("%s, %s, %s, %s: %t, supposed to be %t", sub, org, obj, act, myRes, res)
}
}
func TestOrBACModel(t *testing.T) {
e, err := NewEnforcer("examples/orbac_model.conf", "examples/orbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Test alice as manager in org1 - can consult (read) and modify (write) documents
testEnforceOrBAC(t, e, "alice", "org1", "data1", "read", true)
testEnforceOrBAC(t, e, "alice", "org1", "data1", "write", true)
testEnforceOrBAC(t, e, "alice", "org1", "data2", "read", true)
testEnforceOrBAC(t, e, "alice", "org1", "data2", "write", true)
// Test bob as employee in org1 - can only consult (read) documents
testEnforceOrBAC(t, e, "bob", "org1", "data1", "read", true)
testEnforceOrBAC(t, e, "bob", "org1", "data1", "write", false)
testEnforceOrBAC(t, e, "bob", "org1", "data2", "read", true)
testEnforceOrBAC(t, e, "bob", "org1", "data2", "write", false)
// Test charlie as manager in org2 - can consult and modify reports
testEnforceOrBAC(t, e, "charlie", "org2", "report1", "read", true)
testEnforceOrBAC(t, e, "charlie", "org2", "report1", "write", true)
testEnforceOrBAC(t, e, "charlie", "org2", "report2", "read", true)
testEnforceOrBAC(t, e, "charlie", "org2", "report2", "write", true)
// Test david as employee in org2 - can only consult reports
testEnforceOrBAC(t, e, "david", "org2", "report1", "read", true)
testEnforceOrBAC(t, e, "david", "org2", "report1", "write", false)
testEnforceOrBAC(t, e, "david", "org2", "report2", "read", true)
testEnforceOrBAC(t, e, "david", "org2", "report2", "write", false)
// Test cross-organization access (should be denied)
testEnforceOrBAC(t, e, "alice", "org2", "report1", "read", false)
testEnforceOrBAC(t, e, "alice", "org2", "report1", "write", false)
testEnforceOrBAC(t, e, "charlie", "org1", "data1", "read", false)
testEnforceOrBAC(t, e, "charlie", "org1", "data1", "write", false)
// Test access to objects not in the organization's view
testEnforceOrBAC(t, e, "alice", "org1", "report1", "read", false)
testEnforceOrBAC(t, e, "charlie", "org2", "data1", "read", false)
}
================================================
FILE: pbac_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
// Helper function for PBAC enforcement testing.
func testEnforcePBAC(t *testing.T, e *Enforcer, sub interface{}, obj interface{}, act string, res bool) {
t.Helper()
myRes, err := e.Enforce(sub, obj, act)
if err != nil {
t.Errorf("Enforce Error: %s", err)
return
}
if myRes != res {
t.Errorf("%v, %v, %s: %t, supposed to be %t", sub, obj, act, myRes, res)
}
}
func TestPBACModel(t *testing.T) {
e, _ := NewEnforcer("examples/pbac_model.conf", "examples/pbac_policy.csv")
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 25}, map[string]interface{}{"Type": "doc"}, "read", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 25}, map[string]interface{}{"Type": "doc"}, "read", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "manager", "Age": 30}, map[string]interface{}{"Type": "doc"}, "read", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 25}, map[string]interface{}{"Type": "doc"}, "write", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 25}, map[string]interface{}{"Type": "doc"}, "delete", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 25}, map[string]interface{}{"Type": "video"}, "read", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 25}, map[string]interface{}{"Type": "image"}, "read", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 18}, map[string]interface{}{"Type": "video"}, "play", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 25}, map[string]interface{}{"Type": "video"}, "play", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 30}, map[string]interface{}{"Type": "video"}, "play", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 16}, map[string]interface{}{"Type": "video"}, "play", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 17}, map[string]interface{}{"Type": "video"}, "play", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 20}, map[string]interface{}{"Type": "video"}, "read", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 20}, map[string]interface{}{"Type": "video"}, "write", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 20}, map[string]interface{}{"Type": "doc"}, "play", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "user", "Age": 20}, map[string]interface{}{"Type": "image"}, "play", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 20}, map[string]interface{}{"Type": "doc"}, "read", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "admin", "Age": 20}, map[string]interface{}{"Type": "video"}, "play", true)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "guest", "Age": 15}, map[string]interface{}{"Type": "secret"}, "access", false)
testEnforcePBAC(t, e, map[string]interface{}{"Role": "visitor", "Age": 25}, map[string]interface{}{"Type": "private"}, "view", false)
}
================================================
FILE: persist/adapter.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import (
"encoding/csv"
"strings"
"github.com/casbin/casbin/v3/model"
)
// LoadPolicyLine loads a text line as a policy rule to model.
func LoadPolicyLine(line string, m model.Model) error {
if line == "" || strings.HasPrefix(line, "#") {
return nil
}
r := csv.NewReader(strings.NewReader(line))
r.Comma = ','
r.Comment = '#'
r.TrimLeadingSpace = true
tokens, err := r.Read()
if err != nil {
return err
}
return LoadPolicyArray(tokens, m)
}
// LoadPolicyArray loads a policy rule to model.
func LoadPolicyArray(rule []string, m model.Model) error {
key := rule[0]
sec := key[:1]
ok, err := m.HasPolicyEx(sec, key, rule[1:])
if err != nil {
return err
}
if ok {
return nil // skip duplicated policy
}
err = m.AddPolicy(sec, key, rule[1:])
if err != nil {
return err
}
return nil
}
// Adapter is the interface for Casbin adapters.
type Adapter interface {
// LoadPolicy loads all policy rules from the storage.
LoadPolicy(model model.Model) error
// SavePolicy saves all policy rules to the storage.
SavePolicy(model model.Model) error
// AddPolicy adds a policy rule to the storage.
// This is part of the Auto-Save feature.
AddPolicy(sec string, ptype string, rule []string) error
// RemovePolicy removes a policy rule from the storage.
// This is part of the Auto-Save feature.
RemovePolicy(sec string, ptype string, rule []string) error
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
// This is part of the Auto-Save feature.
RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
}
================================================
FILE: persist/adapter_context.go
================================================
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import (
"context"
"github.com/casbin/casbin/v3/model"
)
// ContextAdapter provides a context-aware interface for Casbin adapters.
type ContextAdapter interface {
// LoadPolicyCtx loads all policy rules from the storage with context.
LoadPolicyCtx(ctx context.Context, model model.Model) error
// SavePolicyCtx saves all policy rules to the storage with context.
SavePolicyCtx(ctx context.Context, model model.Model) error
// AddPolicyCtx adds a policy rule to the storage with context.
// This is part of the Auto-Save feature.
AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error
// RemovePolicyCtx removes a policy rule from the storage with context.
// This is part of the Auto-Save feature.
RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error
// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
// This is part of the Auto-Save feature.
RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error
}
================================================
FILE: persist/adapter_filtered.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import (
"github.com/casbin/casbin/v3/model"
)
// FilteredAdapter is the interface for Casbin adapters supporting filtered policies.
type FilteredAdapter interface {
Adapter
// LoadFilteredPolicy loads only policy rules that match the filter.
LoadFilteredPolicy(model model.Model, filter interface{}) error
// IsFiltered returns true if the loaded policy has been filtered.
IsFiltered() bool
}
================================================
FILE: persist/adapter_filtered_context.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import (
"context"
"github.com/casbin/casbin/v3/model"
)
// ContextFilteredAdapter is the context-aware interface for Casbin adapters supporting filtered policies.
type ContextFilteredAdapter interface {
ContextAdapter
// LoadFilteredPolicyCtx loads only policy rules that match the filter.
LoadFilteredPolicyCtx(ctx context.Context, model model.Model, filter interface{}) error
// IsFilteredCtx returns true if the loaded policy has been filtered.
IsFilteredCtx(ctx context.Context) bool
}
================================================
FILE: persist/batch_adapter.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
// BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.
type BatchAdapter interface {
Adapter
// AddPolicies adds policy rules to the storage.
// This is part of the Auto-Save feature.
AddPolicies(sec string, ptype string, rules [][]string) error
// RemovePolicies removes policy rules from the storage.
// This is part of the Auto-Save feature.
RemovePolicies(sec string, ptype string, rules [][]string) error
}
================================================
FILE: persist/batch_adapter_context.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import "context"
// ContextBatchAdapter is the context-aware interface for Casbin adapters with multiple add and remove policy functions.
type ContextBatchAdapter interface {
ContextAdapter
// AddPoliciesCtx adds policy rules to the storage.
// This is part of the Auto-Save feature.
AddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error
// RemovePoliciesCtx removes policy rules from the storage.
// This is part of the Auto-Save feature.
RemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error
}
================================================
FILE: persist/cache/cache.go
================================================
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import "errors"
var ErrNoSuchKey = errors.New("there's no such key existing in cache")
type Cache interface {
// Set puts key and value into cache.
// First parameter for extra should be time.Time object denoting expected survival time.
// If survival time equals 0 or less, the key will always be survival.
Set(key string, value bool, extra ...interface{}) error
// Get returns result for key,
// If there's no such key existing in cache,
// ErrNoSuchKey will be returned.
Get(key string) (bool, error)
// Delete will remove the specific key in cache.
// If there's no such key existing in cache,
// ErrNoSuchKey will be returned.
Delete(key string) error
// Clear deletes all the items stored in cache.
Clear() error
}
================================================
FILE: persist/cache/cache_sync.go
================================================
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"sync"
"time"
)
type SyncCache struct {
cache DefaultCache
sync.RWMutex
}
func (c *SyncCache) Set(key string, value bool, extra ...interface{}) error {
ttl := time.Duration(-1)
if len(extra) > 0 {
ttl = extra[0].(time.Duration)
}
c.Lock()
defer c.Unlock()
c.cache[key] = cacheItem{
value: value,
expiresAt: time.Now().Add(ttl),
ttl: ttl,
}
return nil
}
func (c *SyncCache) Get(key string) (bool, error) {
c.RLock()
res, ok := c.cache[key]
c.RUnlock()
if !ok {
return false, ErrNoSuchKey
} else {
if res.ttl > 0 && time.Now().After(res.expiresAt) {
c.Lock()
defer c.Unlock()
delete(c.cache, key)
return false, ErrNoSuchKey
}
return res.value, nil
}
}
func (c *SyncCache) Delete(key string) error {
c.RLock()
_, ok := c.cache[key]
c.RUnlock()
if !ok {
return ErrNoSuchKey
} else {
c.Lock()
defer c.Unlock()
delete(c.cache, key)
return nil
}
}
func (c *SyncCache) Clear() error {
c.Lock()
c.cache = make(DefaultCache)
c.Unlock()
return nil
}
func NewSyncCache() (Cache, error) {
cache := SyncCache{
make(DefaultCache),
sync.RWMutex{},
}
return &cache, nil
}
================================================
FILE: persist/cache/default-cache.go
================================================
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import "time"
type cacheItem struct {
value bool
expiresAt time.Time
ttl time.Duration
}
type DefaultCache map[string]cacheItem
func (c *DefaultCache) Set(key string, value bool, extra ...interface{}) error {
ttl := time.Duration(-1)
if len(extra) > 0 {
ttl = extra[0].(time.Duration)
}
(*c)[key] = cacheItem{
value: value,
expiresAt: time.Now().Add(ttl),
ttl: ttl,
}
return nil
}
func (c *DefaultCache) Get(key string) (bool, error) {
if res, ok := (*c)[key]; !ok {
return false, ErrNoSuchKey
} else {
if res.ttl > 0 && time.Now().After(res.expiresAt) {
delete(*c, key)
return false, ErrNoSuchKey
}
return res.value, nil
}
}
func (c *DefaultCache) Delete(key string) error {
if _, ok := (*c)[key]; !ok {
return ErrNoSuchKey
} else {
delete(*c, key)
return nil
}
}
func (c *DefaultCache) Clear() error {
*c = make(DefaultCache)
return nil
}
func NewDefaultCache() (Cache, error) {
cache := make(DefaultCache)
return &cache, nil
}
================================================
FILE: persist/dispatcher.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
// Dispatcher is the interface for Casbin dispatcher.
type Dispatcher interface {
// AddPolicies adds policies rule to all instance.
AddPolicies(sec string, ptype string, rules [][]string) error
// RemovePolicies removes policies rule from all instance.
RemovePolicies(sec string, ptype string, rules [][]string) error
// RemoveFilteredPolicy removes policy rules that match the filter from all instance.
RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
// ClearPolicy clears all current policy in all instances
ClearPolicy() error
// UpdatePolicy updates policy rule from all instance.
UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error
// UpdatePolicies updates some policy rules from all instance
UpdatePolicies(sec string, ptype string, oldrules, newRules [][]string) error
// UpdateFilteredPolicies deletes old rules and adds new rules.
UpdateFilteredPolicies(sec string, ptype string, oldRules [][]string, newRules [][]string) error
}
================================================
FILE: persist/file-adapter/adapter.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileadapter
import (
"bufio"
"bytes"
"errors"
"os"
"strings"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
"github.com/casbin/casbin/v3/util"
)
// Adapter is the file adapter for Casbin.
// It can load policy from file or save policy to file.
type Adapter struct {
filePath string
}
func (a *Adapter) UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error {
return errors.New("not implemented")
}
func (a *Adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {
return errors.New("not implemented")
}
func (a *Adapter) UpdateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
return nil, errors.New("not implemented")
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(filePath string) *Adapter {
return &Adapter{filePath: filePath}
}
// LoadPolicy loads all policy rules from the storage.
func (a *Adapter) LoadPolicy(model model.Model) error {
if a.filePath == "" {
return errors.New("invalid file path, file path cannot be empty")
}
return a.loadPolicyFile(model, persist.LoadPolicyLine)
}
// SavePolicy saves all policy rules to the storage.
func (a *Adapter) SavePolicy(model model.Model) error {
if a.filePath == "" {
return errors.New("invalid file path, file path cannot be empty")
}
var tmp bytes.Buffer
for ptype, ast := range model["p"] {
for _, rule := range ast.Policy {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
for ptype, ast := range model["g"] {
for _, rule := range ast.Policy {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
return a.savePolicyFile(strings.TrimRight(tmp.String(), "\n"))
}
func (a *Adapter) loadPolicyFile(model model.Model, handler func(string, model.Model) error) error {
f, err := os.Open(a.filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
err = handler(line, model)
if err != nil {
return err
}
}
return scanner.Err()
}
func (a *Adapter) savePolicyFile(text string) error {
f, err := os.Create(a.filePath)
if err != nil {
return err
}
w := bufio.NewWriter(f)
_, err = w.WriteString(text)
if err != nil {
return err
}
err = w.Flush()
if err != nil {
return err
}
return f.Close()
}
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}
// AddPolicies adds policy rules to the storage.
func (a *Adapter) AddPolicies(sec string, ptype string, rules [][]string) error {
return errors.New("not implemented")
}
// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}
// RemovePolicies removes policy rules from the storage.
func (a *Adapter) RemovePolicies(sec string, ptype string, rules [][]string) error {
return errors.New("not implemented")
}
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return errors.New("not implemented")
}
================================================
FILE: persist/file-adapter/adapter_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileadapter
import (
"context"
"github.com/casbin/casbin/v3/model"
)
func (a *Adapter) UpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.UpdatePolicy(sec, ptype, oldRule, newRule)
}
func (a *Adapter) UpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.UpdatePolicies(sec, ptype, oldRules, newRules)
}
func (a *Adapter) UpdateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {
if err := checkCtx(ctx); err != nil {
return nil, err
}
return a.UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...)
}
// LoadPolicyCtx loads all policy rules from the storage with context.
func (a *Adapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.LoadPolicy(model)
}
// SavePolicyCtx saves all policy rules to the storage with context.
func (a *Adapter) SavePolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.SavePolicy(model)
}
// AddPolicyCtx adds a policy rule to the storage with context.
func (a *Adapter) AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.AddPolicy(sec, ptype, rule)
}
// AddPoliciesCtx adds policy rules to the storage with context.
func (a *Adapter) AddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.AddPolicies(sec, ptype, rules)
}
// RemovePolicyCtx removes a policy rule from the storage with context.
func (a *Adapter) RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.RemovePolicy(sec, ptype, rule)
}
// RemovePoliciesCtx removes policy rules from the storage with context.
func (a *Adapter) RemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.RemovePolicies(sec, ptype, rules)
}
// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
func (a *Adapter) RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
}
func checkCtx(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}
================================================
FILE: persist/file-adapter/adapter_filtered.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileadapter
import (
"bufio"
"errors"
"os"
"strings"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// FilteredAdapter is the filtered file adapter for Casbin. It can load policy
// from file or save policy to file and supports loading of filtered policies.
type FilteredAdapter struct {
*Adapter
filtered bool
}
// Filter defines the filtering rules for a FilteredAdapter's policy. Empty values
// are ignored, but all others must match the filter.
type Filter struct {
P []string
G []string
G1 []string
G2 []string
G3 []string
G4 []string
G5 []string
}
// NewFilteredAdapter is the constructor for FilteredAdapter.
func NewFilteredAdapter(filePath string) *FilteredAdapter {
a := FilteredAdapter{}
a.filtered = true
a.Adapter = NewAdapter(filePath)
return &a
}
// LoadPolicy loads all policy rules from the storage.
func (a *FilteredAdapter) LoadPolicy(model model.Model) error {
a.filtered = false
return a.Adapter.LoadPolicy(model)
}
// LoadFilteredPolicy loads only policy rules that match the filter.
func (a *FilteredAdapter) LoadFilteredPolicy(model model.Model, filter interface{}) error {
if filter == nil {
return a.LoadPolicy(model)
}
if a.filePath == "" {
return errors.New("invalid file path, file path cannot be empty")
}
filterValue, ok := filter.(*Filter)
if !ok {
return errors.New("invalid filter type")
}
err := a.loadFilteredPolicyFile(model, filterValue, persist.LoadPolicyLine)
if err == nil {
a.filtered = true
}
return err
}
func (a *FilteredAdapter) loadFilteredPolicyFile(model model.Model, filter *Filter, handler func(string, model.Model) error) error {
f, err := os.Open(a.filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if filterLine(line, filter) {
continue
}
err = handler(line, model)
if err != nil {
return err
}
}
return scanner.Err()
}
// IsFiltered returns true if the loaded policy has been filtered.
func (a *FilteredAdapter) IsFiltered() bool {
return a.filtered
}
// SavePolicy saves all policy rules to the storage.
func (a *FilteredAdapter) SavePolicy(model model.Model) error {
if a.filtered {
return errors.New("cannot save a filtered policy")
}
return a.Adapter.SavePolicy(model)
}
func filterLine(line string, filter *Filter) bool {
if filter == nil {
return false
}
p := strings.Split(line, ",")
if len(p) == 0 {
return true
}
var filterSlice []string
switch strings.TrimSpace(p[0]) {
case "p":
filterSlice = filter.P
case "g":
filterSlice = filter.G
case "g1":
filterSlice = filter.G1
case "g2":
filterSlice = filter.G2
case "g3":
filterSlice = filter.G3
case "g4":
filterSlice = filter.G4
case "g5":
filterSlice = filter.G5
}
return filterWords(p, filterSlice)
}
func filterWords(line []string, filter []string) bool {
if len(line) < len(filter)+1 {
return true
}
var skipLine bool
for i, v := range filter {
if len(v) > 0 && strings.TrimSpace(v) != strings.TrimSpace(line[i+1]) {
skipLine = true
break
}
}
return skipLine
}
================================================
FILE: persist/file-adapter/adapter_filtered_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileadapter
import (
"context"
"github.com/casbin/casbin/v3/model"
)
// LoadPolicyCtx loads all policy rules from the storage with context.
func (a *FilteredAdapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.LoadPolicy(model)
}
// LoadFilteredPolicyCtx loads only policy rules that match the filter with context.
func (a *FilteredAdapter) LoadFilteredPolicyCtx(ctx context.Context, model model.Model, filter interface{}) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.LoadFilteredPolicy(model, filter)
}
// SavePolicyCtx saves all policy rules to the storage with context.
func (a *FilteredAdapter) SavePolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.SavePolicy(model)
}
// IsFilteredCtx returns true if the loaded policy has been filtered with context.
func (a *FilteredAdapter) IsFilteredCtx(ctx context.Context) bool {
if err := checkCtx(ctx); err != nil {
return false
}
return a.IsFiltered()
}
================================================
FILE: persist/file-adapter/adapter_mock.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fileadapter
import (
"bufio"
"errors"
"io"
"os"
"strings"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// AdapterMock is the file adapter for Casbin.
// It can load policy from file or save policy to file.
type AdapterMock struct {
filePath string
errorValue string
}
// NewAdapterMock is the constructor for AdapterMock.
func NewAdapterMock(filePath string) *AdapterMock {
a := AdapterMock{}
a.filePath = filePath
return &a
}
// LoadPolicy loads all policy rules from the storage.
func (a *AdapterMock) LoadPolicy(model model.Model) error {
err := a.loadPolicyFile(model, persist.LoadPolicyLine)
return err
}
// SavePolicy saves all policy rules to the storage.
func (a *AdapterMock) SavePolicy(model model.Model) error {
return nil
}
func (a *AdapterMock) loadPolicyFile(model model.Model, handler func(string, model.Model) error) error {
f, err := os.Open(a.filePath)
if err != nil {
return err
}
defer f.Close()
buf := bufio.NewReader(f)
for {
line, err := buf.ReadString('\n')
line = strings.TrimSpace(line)
if err2 := handler(line, model); err2 != nil {
return err2
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
// SetMockErr sets string to be returned by of the mock during testing.
func (a *AdapterMock) SetMockErr(errorToSet string) {
a.errorValue = errorToSet
}
// GetMockErr returns a mock error or nil.
func (a *AdapterMock) GetMockErr() error {
var returnError error
if a.errorValue != "" {
returnError = errors.New(a.errorValue)
}
return returnError
}
// AddPolicy adds a policy rule to the storage.
func (a *AdapterMock) AddPolicy(sec string, ptype string, rule []string) error {
return a.GetMockErr()
}
// AddPolicies removes policy rules from the storage.
func (a *AdapterMock) AddPolicies(sec string, ptype string, rules [][]string) error {
return a.GetMockErr()
}
// RemovePolicy removes a policy rule from the storage.
func (a *AdapterMock) RemovePolicy(sec string, ptype string, rule []string) error {
return a.GetMockErr()
}
// RemovePolicies removes policy rules from the storage.
func (a *AdapterMock) RemovePolicies(sec string, ptype string, rules [][]string) error {
return a.GetMockErr()
}
// UpdatePolicy removes a policy rule from the storage.
func (a *AdapterMock) UpdatePolicy(sec string, ptype string, oldRule, newPolicy []string) error {
return a.GetMockErr()
}
func (a *AdapterMock) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {
return a.GetMockErr()
}
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *AdapterMock) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return a.GetMockErr()
}
================================================
FILE: persist/persist_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist_test
import (
"testing"
"github.com/casbin/casbin/v3"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
func TestPersist(t *testing.T) {
// No tests yet
}
func testRuleCount(t *testing.T, model model.Model, expected int, sec string, ptype string, tag string) {
t.Helper()
ruleCount := len(model[sec][ptype].Policy)
if ruleCount != expected {
t.Errorf("[%s] rule count: %d, expected %d", tag, ruleCount, expected)
}
}
func TestDuplicateRuleInAdapter(t *testing.T) {
e, _ := casbin.NewEnforcer("../examples/basic_model.conf")
_, _ = e.AddPolicy("alice", "data1", "read")
_, _ = e.AddPolicy("alice", "data1", "read")
testRuleCount(t, e.GetModel(), 1, "p", "p", "AddPolicy")
e.ClearPolicy()
// simulate adapter.LoadPolicy with duplicate rules
_ = persist.LoadPolicyArray([]string{"p", "alice", "data1", "read"}, e.GetModel())
_ = persist.LoadPolicyArray([]string{"p", "alice", "data1", "read"}, e.GetModel())
testRuleCount(t, e.GetModel(), 1, "p", "p", "LoadPolicyArray")
}
================================================
FILE: persist/string-adapter/adapter.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stringadapter
import (
"bytes"
"errors"
"strings"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
"github.com/casbin/casbin/v3/util"
)
// Adapter is the string adapter for Casbin.
// It can load policy from string or save policy to string.
type Adapter struct {
Line string
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(line string) *Adapter {
return &Adapter{
Line: line,
}
}
// LoadPolicy loads all policy rules from the storage.
func (a *Adapter) LoadPolicy(model model.Model) error {
if a.Line == "" {
return errors.New("invalid line, line cannot be empty")
}
strs := strings.Split(a.Line, "\n")
for _, str := range strs {
if str == "" {
continue
}
_ = persist.LoadPolicyLine(str, model)
}
return nil
}
// SavePolicy saves all policy rules to the storage.
func (a *Adapter) SavePolicy(model model.Model) error {
var tmp bytes.Buffer
for ptype, ast := range model["p"] {
for _, rule := range ast.Policy {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
for ptype, ast := range model["g"] {
for _, rule := range ast.Policy {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
a.Line = strings.TrimRight(tmp.String(), "\n")
return nil
}
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}
// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
a.Line = ""
return nil
}
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return errors.New("not implemented")
}
================================================
FILE: persist/string-adapter/adapter_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stringadapter
import (
"context"
"github.com/casbin/casbin/v3/model"
)
// LoadPolicyCtx loads all policy rules from the storage with context.
func (a *Adapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.LoadPolicy(model)
}
// SavePolicyCtx saves all policy rules to the storage with context.
func (a *Adapter) SavePolicyCtx(ctx context.Context, model model.Model) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.SavePolicy(model)
}
// AddPolicyCtx adds a policy rule to the storage with context.
func (a *Adapter) AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.AddPolicy(sec, ptype, rule)
}
// RemovePolicyCtx removes a policy rule from the storage with context.
func (a *Adapter) RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.RemovePolicy(sec, ptype, rule)
}
// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.
func (a *Adapter) RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error {
if err := checkCtx(ctx); err != nil {
return err
}
return a.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)
}
func checkCtx(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return nil
}
}
================================================
FILE: persist/string-adapter/adapter_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stringadapter
import (
"testing"
"github.com/casbin/casbin/v3"
"github.com/casbin/casbin/v3/model"
)
func Test_KeyMatchRbac(t *testing.T) {
conf := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _ , _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
`
line := `
p, alice, /alice_data/*, (GET)|(POST)
p, alice, /alice_data/resource1, POST
p, data_group_admin, /admin/*, POST
p, data_group_admin, /bob_data/*, POST
g, alice, data_group_admin
`
a := NewAdapter(line)
m := model.NewModel()
err := m.LoadModelFromText(conf)
if err != nil {
t.Errorf("load model from text failed: %v", err.Error())
return
}
e, _ := casbin.NewEnforcer(m, a)
sub := "alice"
obj := "/alice_data/login"
act := "POST"
if res, _ := e.Enforce(sub, obj, act); !res {
t.Error("unexpected enforce result")
}
}
func Test_StringRbac(t *testing.T) {
conf := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _ , _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
line := `
p, alice, data1, read
p, data_group_admin, data3, read
p, data_group_admin, data3, write
g, alice, data_group_admin
`
a := NewAdapter(line)
m := model.NewModel()
err := m.LoadModelFromText(conf)
if err != nil {
t.Errorf("load model from text failed: %v", err.Error())
return
}
e, _ := casbin.NewEnforcer(m, a)
sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.
if res, _ := e.Enforce(sub, obj, act); !res {
t.Error("unexpected enforce result")
}
}
================================================
FILE: persist/transaction.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import "context"
// TransactionalAdapter defines the interface for adapters that support transactions.
// Adapters implementing this interface can participate in Casbin transactions.
type TransactionalAdapter interface {
Adapter
// BeginTransaction starts a new transaction and returns a transaction context.
BeginTransaction(ctx context.Context) (TransactionContext, error)
}
// TransactionContext represents a database transaction context.
// It provides methods to commit or rollback the transaction and get an adapter
// that operates within this transaction.
type TransactionContext interface {
// Commit commits the transaction.
Commit() error
// Rollback rolls back the transaction.
Rollback() error
// GetAdapter returns an adapter that operates within this transaction.
GetAdapter() Adapter
}
// PolicyOperation represents a policy operation that can be buffered in a transaction.
type PolicyOperation struct {
Type OperationType // The type of operation (add, remove, update)
Section string // The section of the policy (p, g)
PolicyType string // The policy type (p, p2, g, g2, etc.)
Rules [][]string // The policy rules to operate on
OldRules [][]string // For update operations, the old rules to replace
}
// OperationType represents the type of policy operation.
type OperationType int
const (
// OperationAdd represents adding policy rules.
OperationAdd OperationType = iota
// OperationRemove represents removing policy rules.
OperationRemove
// OperationUpdate represents updating policy rules.
OperationUpdate
)
================================================
FILE: persist/update_adapter.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
// UpdatableAdapter is the interface for Casbin adapters with add update policy function.
type UpdatableAdapter interface {
Adapter
// UpdatePolicy updates a policy rule from storage.
// This is part of the Auto-Save feature.
UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error
// UpdatePolicies updates some policy rules to storage, like db, redis.
UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error
// UpdateFilteredPolicies deletes old rules and adds new rules.
UpdateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error)
}
================================================
FILE: persist/update_adapter_context.go
================================================
// Copyright 2024 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import "context"
// ContextUpdatableAdapter is the context-aware interface for Casbin adapters with add update policy function.
type ContextUpdatableAdapter interface {
ContextAdapter
// UpdatePolicyCtx updates a policy rule from storage.
// This is part of the Auto-Save feature.
UpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) error
// UpdatePoliciesCtx updates some policy rules to storage, like db, redis.
UpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) error
// UpdateFilteredPoliciesCtx deletes old rules and adds new rules.
UpdateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error)
}
================================================
FILE: persist/watcher.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
// Watcher is the interface for Casbin watchers.
type Watcher interface {
// SetUpdateCallback sets the callback function that the watcher will call
// when the policy in DB has been changed by other instances.
// A classic callback is Enforcer.LoadPolicy().
SetUpdateCallback(func(string)) error
// Update calls the update callback of other instances to synchronize their policy.
// It is usually called after changing the policy in DB, like Enforcer.SavePolicy(),
// Enforcer.AddPolicy(), Enforcer.RemovePolicy(), etc.
Update() error
// Close stops and releases the watcher, the callback function will not be called any more.
Close()
}
================================================
FILE: persist/watcher_ex.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
import "github.com/casbin/casbin/v3/model"
// WatcherEx is the strengthened Casbin watchers.
type WatcherEx interface {
Watcher
// UpdateForAddPolicy calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.AddPolicy()
UpdateForAddPolicy(sec, ptype string, params ...string) error
// UpdateForRemovePolicy calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.RemovePolicy()
UpdateForRemovePolicy(sec, ptype string, params ...string) error
// UpdateForRemoveFilteredPolicy calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.RemoveFilteredNamedGroupingPolicy()
UpdateForRemoveFilteredPolicy(sec, ptype string, fieldIndex int, fieldValues ...string) error
// UpdateForSavePolicy calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.RemoveFilteredNamedGroupingPolicy()
UpdateForSavePolicy(model model.Model) error
// UpdateForAddPolicies calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.AddPolicies()
UpdateForAddPolicies(sec string, ptype string, rules ...[]string) error
// UpdateForRemovePolicies calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.RemovePolicies()
UpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error
}
================================================
FILE: persist/watcher_update.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package persist
// UpdatableWatcher is strengthened for Casbin watchers.
type UpdatableWatcher interface {
Watcher
// UpdateForUpdatePolicy calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.UpdatePolicy()
UpdateForUpdatePolicy(sec string, ptype string, oldRule, newRule []string) error
// UpdateForUpdatePolicies calls the update callback of other instances to synchronize their policy.
// It is called after Enforcer.UpdatePolicies()
UpdateForUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error
}
================================================
FILE: rbac/context_role_manager.go
================================================
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rbac
import "context"
// ContextRoleManager provides a context-aware interface to define the operations for managing roles.
// Prefer this over RoleManager interface for context propagation, which is useful for things like handling
// request timeouts.
type ContextRoleManager interface {
RoleManager
// ClearCtx clears all stored data and resets the role manager to the initial state with context.
ClearCtx(ctx context.Context) error
// AddLinkCtx adds the inheritance link between two roles. role: name1 and role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
AddLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error
// DeleteLinkCtx deletes the inheritance link between two roles. role: name1 and role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
DeleteLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error
// HasLinkCtx determines whether a link exists between two roles. role: name1 inherits role: name2 with context.
// domain is a prefix to the roles (can be used for other purposes).
HasLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) (bool, error)
// GetRolesCtx gets the roles that a user inherits with context.
// domain is a prefix to the roles (can be used for other purposes).
GetRolesCtx(ctx context.Context, name string, domain ...string) ([]string, error)
// GetUsersCtx gets the users that inherits a role with context.
// domain is a prefix to the users (can be used for other purposes).
GetUsersCtx(ctx context.Context, name string, domain ...string) ([]string, error)
// GetDomainsCtx gets domains that a user has with context.
GetDomainsCtx(ctx context.Context, name string) ([]string, error)
// GetAllDomainsCtx gets all domains with context.
GetAllDomainsCtx(ctx context.Context) ([]string, error)
}
================================================
FILE: rbac/default-role-manager/role_manager.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package defaultrolemanager
import (
"errors"
"sync"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
const defaultDomain string = ""
// Role represents the data structure for a role in RBAC.
type Role struct {
name string
roles *sync.Map
users *sync.Map
matched *sync.Map
matchedBy *sync.Map
linkConditionFuncMap *sync.Map
linkConditionFuncParamsMap *sync.Map
}
func newRole(name string) *Role {
r := Role{}
r.name = name
r.roles = &sync.Map{}
r.users = &sync.Map{}
r.matched = &sync.Map{}
r.matchedBy = &sync.Map{}
r.linkConditionFuncMap = &sync.Map{}
r.linkConditionFuncParamsMap = &sync.Map{}
return &r
}
func (r *Role) addRole(role *Role) {
r.roles.Store(role.name, role)
role.addUser(r)
}
func (r *Role) removeRole(role *Role) {
r.roles.Delete(role.name)
role.removeUser(r)
}
// should only be called inside addRole.
func (r *Role) addUser(user *Role) {
r.users.Store(user.name, user)
}
// should only be called inside removeRole.
func (r *Role) removeUser(user *Role) {
r.users.Delete(user.name)
}
func (r *Role) addMatch(role *Role) {
r.matched.Store(role.name, role)
role.matchedBy.Store(r.name, r)
}
func (r *Role) removeMatch(role *Role) {
r.matched.Delete(role.name)
role.matchedBy.Delete(r.name)
}
func (r *Role) removeMatches() {
r.matched.Range(func(key, value interface{}) bool {
r.removeMatch(value.(*Role))
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
value.(*Role).removeMatch(r)
return true
})
}
func (r *Role) rangeRoles(fn func(key, value interface{}) bool) {
r.roles.Range(fn)
r.roles.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.matched.Range(fn)
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.roles.Range(fn)
return true
})
}
func (r *Role) rangeUsers(fn func(key, value interface{}) bool) {
r.users.Range(fn)
r.users.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.matched.Range(fn)
return true
})
r.matchedBy.Range(func(key, value interface{}) bool {
role := value.(*Role)
role.users.Range(fn)
return true
})
}
func (r *Role) getRoles() []string {
var names []string
r.rangeRoles(func(key, value interface{}) bool {
names = append(names, key.(string))
return true
})
return util.RemoveDuplicateElement(names)
}
func (r *Role) getUsers() []string {
var names []string
r.rangeUsers(func(key, value interface{}) bool {
names = append(names, key.(string))
return true
})
return names
}
type linkConditionFuncKey struct {
roleName string
domainName string
}
func (r *Role) addLinkConditionFunc(role *Role, domain string, fn rbac.LinkConditionFunc) {
r.linkConditionFuncMap.Store(linkConditionFuncKey{role.name, domain}, fn)
}
func (r *Role) getLinkConditionFunc(role *Role, domain string) (rbac.LinkConditionFunc, bool) {
fn, ok := r.linkConditionFuncMap.Load(linkConditionFuncKey{role.name, domain})
if fn == nil {
return nil, ok
}
return fn.(rbac.LinkConditionFunc), ok
}
func (r *Role) setLinkConditionFuncParams(role *Role, domain string, params ...string) {
r.linkConditionFuncParamsMap.Store(linkConditionFuncKey{role.name, domain}, params)
}
func (r *Role) getLinkConditionFuncParams(role *Role, domain string) ([]string, bool) {
params, ok := r.linkConditionFuncParamsMap.Load(linkConditionFuncKey{role.name, domain})
if params == nil {
return nil, ok
}
return params.([]string), ok
}
// RoleManagerImpl provides a default implementation for the RoleManager interface.
type RoleManagerImpl struct {
allRoles *sync.Map
maxHierarchyLevel int
matchingFunc rbac.MatchingFunc
domainMatchingFunc rbac.MatchingFunc
matchingFuncCache *util.SyncLRUCache
mutex sync.Mutex
}
// NewRoleManagerImpl is the constructor for creating an instance of the
// default RoleManager implementation.
func NewRoleManagerImpl(maxHierarchyLevel int) *RoleManagerImpl {
rm := RoleManagerImpl{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
// use this constructor to avoid rebuild of AddMatchingFunc.
func newRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *RoleManagerImpl {
rm := NewRoleManagerImpl(maxHierarchyLevel)
rm.matchingFunc = fn
return rm
}
// rebuilds role cache.
func (rm *RoleManagerImpl) rebuild() {
roles := rm.allRoles
_ = rm.Clear()
rangeLinks(roles, func(name1, name2 string, domain ...string) bool {
_ = rm.AddLink(name1, name2, domain...)
return true
})
}
func (rm *RoleManagerImpl) Match(str string, pattern string) bool {
if str == pattern {
return true
}
if rm.matchingFunc != nil {
return rm.matchingFunc(str, pattern)
} else {
return false
}
}
func (rm *RoleManagerImpl) rangeMatchingRoles(name string, isPattern bool, fn func(role *Role) bool) {
rm.allRoles.Range(func(key, value interface{}) bool {
name2 := key.(string)
if isPattern && name != name2 && rm.Match(name2, name) {
fn(value.(*Role))
} else if !isPattern && name != name2 && rm.Match(name, name2) {
fn(value.(*Role))
}
return true
})
}
func (rm *RoleManagerImpl) load(name interface{}) (value *Role, ok bool) {
if r, ok := rm.allRoles.Load(name); ok {
return r.(*Role), true
}
return nil, false
}
// loads or creates a role.
func (rm *RoleManagerImpl) getRole(name string) (r *Role, created bool) {
var role *Role
var ok bool
if role, ok = rm.load(name); !ok {
role = newRole(name)
rm.allRoles.Store(name, role)
if rm.matchingFunc != nil {
rm.rangeMatchingRoles(name, false, func(r *Role) bool {
r.addMatch(role)
return true
})
rm.rangeMatchingRoles(name, true, func(r *Role) bool {
role.addMatch(r)
return true
})
}
}
return role, !ok
}
func loadAndDelete(m *sync.Map, name string) (value interface{}, loaded bool) {
value, loaded = m.Load(name)
if loaded {
m.Delete(name)
}
return value, loaded
}
func (rm *RoleManagerImpl) removeRole(name string) {
if role, ok := loadAndDelete(rm.allRoles, name); ok {
role.(*Role).removeMatches()
}
}
// AddMatchingFunc support use pattern in g.
func (rm *RoleManagerImpl) AddMatchingFunc(name string, fn rbac.MatchingFunc) {
rm.matchingFunc = fn
rm.rebuild()
}
// AddDomainMatchingFunc support use domain pattern in g.
func (rm *RoleManagerImpl) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
rm.domainMatchingFunc = fn
}
// Clear clears all stored data and resets the role manager to the initial state.
func (rm *RoleManagerImpl) Clear() error {
rm.matchingFuncCache = util.NewSyncLRUCache(100)
rm.allRoles = &sync.Map{}
return nil
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (rm *RoleManagerImpl) AddLink(name1 string, name2 string, domains ...string) error {
user, _ := rm.getRole(name1)
role, _ := rm.getRole(name2)
user.addRole(role)
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (rm *RoleManagerImpl) DeleteLink(name1 string, name2 string, domains ...string) error {
user, _ := rm.getRole(name1)
role, _ := rm.getRole(name2)
user.removeRole(role)
return nil
}
// HasLink determines whether role: name1 inherits role: name2.
func (rm *RoleManagerImpl) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
if name1 == name2 || (rm.matchingFunc != nil && rm.Match(name1, name2)) {
return true, nil
}
// Lock to prevent race conditions between getRole and removeRole
rm.mutex.Lock()
defer rm.mutex.Unlock()
user, userCreated := rm.getRole(name1)
role, roleCreated := rm.getRole(name2)
if userCreated {
defer rm.removeRole(user.name)
}
if roleCreated {
defer rm.removeRole(role.name)
}
return rm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, rm.maxHierarchyLevel), nil
}
func (rm *RoleManagerImpl) hasLinkHelper(targetName string, roles map[string]*Role, level int) bool {
if level < 0 || len(roles) == 0 {
return false
}
nextRoles := map[string]*Role{}
for _, role := range roles {
if targetName == role.name || (rm.matchingFunc != nil && rm.Match(role.name, targetName)) {
return true
}
role.rangeRoles(func(key, value interface{}) bool {
nextRoles[key.(string)] = value.(*Role)
return true
})
}
return rm.hasLinkHelper(targetName, nextRoles, level-1)
}
// GetRoles gets the roles that a user inherits.
func (rm *RoleManagerImpl) GetRoles(name string, domains ...string) ([]string, error) {
user, created := rm.getRole(name)
if created {
defer rm.removeRole(user.name)
}
return user.getRoles(), nil
}
// GetUsers gets the users of a role.
// domain is an unreferenced parameter here, may be used in other implementations.
func (rm *RoleManagerImpl) GetUsers(name string, domain ...string) ([]string, error) {
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
return role.getUsers(), nil
}
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.
func (rm *RoleManagerImpl) GetImplicitRoles(name string, domain ...string) ([]string, error) {
user, created := rm.getRole(name)
if created {
defer rm.removeRole(user.name)
}
var res []string
roleSet := make(map[string]bool)
roleSet[name] = true
roles := map[string]*Role{user.name: user}
return rm.getImplicitRolesHelper(roles, roleSet, res, 0), nil
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
func (rm *RoleManagerImpl) GetImplicitUsers(name string, domain ...string) ([]string, error) {
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
var res []string
userSet := make(map[string]bool)
userSet[name] = true
users := map[string]*Role{role.name: role}
return rm.getImplicitUsersHelper(users, userSet, res, 0), nil
}
// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel.
func (rm *RoleManagerImpl) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int) []string {
if level >= rm.maxHierarchyLevel || len(roles) == 0 {
return res
}
nextRoles := map[string]*Role{}
for _, role := range roles {
role.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
if _, ok := roleSet[roleName]; !ok {
res = append(res, roleName)
roleSet[roleName] = true
nextRoles[roleName] = value.(*Role)
}
return true
})
}
return rm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1)
}
// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel.
func (rm *RoleManagerImpl) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int) []string {
if level >= rm.maxHierarchyLevel || len(users) == 0 {
return res
}
nextUsers := map[string]*Role{}
for _, user := range users {
user.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
if _, ok := userSet[userName]; !ok {
res = append(res, userName)
userSet[userName] = true
nextUsers[userName] = value.(*Role)
}
return true
})
}
return rm.getImplicitUsersHelper(nextUsers, userSet, res, level+1)
}
// PrintRoles prints all the roles to log.
func (rm *RoleManagerImpl) PrintRoles() error {
// Logger has been removed - this is now a no-op
return nil
}
// GetDomains gets domains that a user has.
func (rm *RoleManagerImpl) GetDomains(name string) ([]string, error) {
domains := []string{defaultDomain}
return domains, nil
}
// GetAllDomains gets all domains.
func (rm *RoleManagerImpl) GetAllDomains() ([]string, error) {
domains := []string{defaultDomain}
return domains, nil
}
func (rm *RoleManagerImpl) copyFrom(other *RoleManagerImpl) {
other.Range(func(name1, name2 string, domain ...string) bool {
_ = rm.AddLink(name1, name2, domain...)
return true
})
}
func rangeLinks(users *sync.Map, fn func(name1, name2 string, domain ...string) bool) {
users.Range(func(_, value interface{}) bool {
user := value.(*Role)
user.roles.Range(func(key, _ interface{}) bool {
roleName := key.(string)
return fn(user.name, roleName, defaultDomain)
})
return true
})
}
func (rm *RoleManagerImpl) Range(fn func(name1, name2 string, domain ...string) bool) {
rangeLinks(rm.allRoles, fn)
}
// Deprecated: BuildRelationship is no longer required.
func (rm *RoleManagerImpl) BuildRelationship(name1 string, name2 string, domain ...string) error {
return nil
}
type DomainManager struct {
rmMap *sync.Map
maxHierarchyLevel int
matchingFunc rbac.MatchingFunc
domainMatchingFunc rbac.MatchingFunc
matchingFuncCache *util.SyncLRUCache
}
// NewDomainManager is the constructor for creating an instance of the
// default DomainManager implementation.
func NewDomainManager(maxHierarchyLevel int) *DomainManager {
dm := &DomainManager{}
_ = dm.Clear() // init rmMap and rmCache
dm.maxHierarchyLevel = maxHierarchyLevel
return dm
}
// AddMatchingFunc support use pattern in g.
func (dm *DomainManager) AddMatchingFunc(name string, fn rbac.MatchingFunc) {
dm.matchingFunc = fn
dm.rmMap.Range(func(key, value interface{}) bool {
value.(*RoleManagerImpl).AddMatchingFunc(name, fn)
return true
})
}
// AddDomainMatchingFunc support use domain pattern in g.
func (dm *DomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
dm.domainMatchingFunc = fn
dm.rmMap.Range(func(key, value interface{}) bool {
value.(*RoleManagerImpl).AddDomainMatchingFunc(name, fn)
return true
})
dm.rebuild()
}
// clears the map of RoleManagers.
func (dm *DomainManager) rebuild() {
rmMap := dm.rmMap
_ = dm.Clear()
rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
rm := value.(*RoleManagerImpl)
rm.Range(func(name1, name2 string, _ ...string) bool {
_ = dm.AddLink(name1, name2, domain)
return true
})
return true
})
}
// Clear clears all stored data and resets the role manager to the initial state.
func (dm *DomainManager) Clear() error {
dm.rmMap = &sync.Map{}
dm.matchingFuncCache = util.NewSyncLRUCache(100)
return nil
}
func (dm *DomainManager) getDomain(domains ...string) (domain string, err error) {
switch len(domains) {
case 0:
return defaultDomain, nil
default:
return domains[0], nil
}
}
func (dm *DomainManager) Match(str string, pattern string) bool {
if str == pattern {
return true
}
if dm.domainMatchingFunc != nil {
return dm.domainMatchingFunc(str, pattern)
} else {
return false
}
}
func (dm *DomainManager) rangeAffectedRoleManagers(domain string, fn func(rm *RoleManagerImpl)) {
if dm.domainMatchingFunc != nil {
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
if domain != domain2 && dm.Match(domain2, domain) {
fn(value.(*RoleManagerImpl))
}
return true
})
}
}
func (dm *DomainManager) load(name interface{}) (value *RoleManagerImpl, ok bool) {
if r, ok := dm.rmMap.Load(name); ok {
return r.(*RoleManagerImpl), true
}
return nil, false
}
// load or create a RoleManager instance of domain.
func (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerImpl {
var rm *RoleManagerImpl
var ok bool
if rm, ok = dm.load(domain); !ok {
rm = newRoleManagerWithMatchingFunc(dm.maxHierarchyLevel, dm.matchingFunc)
if store {
dm.rmMap.Store(domain, rm)
}
if dm.domainMatchingFunc != nil {
dm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
rm2 := value.(*RoleManagerImpl)
if domain != domain2 && dm.Match(domain, domain2) {
rm.copyFrom(rm2)
}
return true
})
}
}
return rm
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (dm *DomainManager) AddLink(name1 string, name2 string, domains ...string) error {
domain, err := dm.getDomain(domains...)
if err != nil {
return err
}
roleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist
_ = roleManager.AddLink(name1, name2, domains...)
dm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.AddLink(name1, name2, domains...)
})
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (dm *DomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {
domain, err := dm.getDomain(domains...)
if err != nil {
return err
}
roleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist
_ = roleManager.DeleteLink(name1, name2, domains...)
dm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.DeleteLink(name1, name2, domains...)
})
return nil
}
// HasLink determines whether role: name1 inherits role: name2.
func (dm *DomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return false, err
}
rm := dm.getRoleManager(domain, false)
return rm.HasLink(name1, name2, domains...)
}
// GetRoles gets the roles that a subject inherits.
func (dm *DomainManager) GetRoles(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetRoles(name, domains...)
}
// GetUsers gets the users of a role.
func (dm *DomainManager) GetUsers(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetUsers(name, domains...)
}
// GetImplicitRoles gets the implicit roles that a subject inherits, respecting maxHierarchyLevel.
func (dm *DomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetImplicitRoles(name, domains...)
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
func (dm *DomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {
domain, err := dm.getDomain(domains...)
if err != nil {
return nil, err
}
rm := dm.getRoleManager(domain, false)
return rm.GetImplicitUsers(name, domains...)
}
// PrintRoles prints all the roles to log.
func (dm *DomainManager) PrintRoles() error {
// Logger has been removed - this is now a no-op
return nil
}
// GetDomains gets domains that a user has.
func (dm *DomainManager) GetDomains(name string) ([]string, error) {
var domains []string
dm.rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
rm := value.(*RoleManagerImpl)
role, created := rm.getRole(name)
if created {
defer rm.removeRole(role.name)
}
if len(role.getUsers()) > 0 || len(role.getRoles()) > 0 {
domains = append(domains, domain)
}
return true
})
return domains, nil
}
// GetAllDomains gets all domains.
func (dm *DomainManager) GetAllDomains() ([]string, error) {
var domains []string
dm.rmMap.Range(func(key, value interface{}) bool {
domains = append(domains, key.(string))
return true
})
return domains, nil
}
// Deprecated: BuildRelationship is no longer required.
func (dm *DomainManager) BuildRelationship(name1 string, name2 string, domain ...string) error {
return nil
}
// DeleteDomain deletes the specified domain from DomainManager.
func (dm *DomainManager) DeleteDomain(domain string) error {
dm.rmMap.Delete(domain)
return nil
}
type RoleManager struct {
*DomainManager
}
func NewRoleManager(maxHierarchyLevel int) *RoleManager {
rm := &RoleManager{}
rm.DomainManager = NewDomainManager(maxHierarchyLevel)
return rm
}
// DeleteDomain does nothing for RoleManagerImpl (no domain concept).
func (rm *RoleManagerImpl) DeleteDomain(domain string) error {
return errors.New("DeleteDomain is not supported by RoleManagerImpl (no domain concept)")
}
type ConditionalRoleManager struct {
RoleManagerImpl
}
func (crm *ConditionalRoleManager) copyFrom(other *ConditionalRoleManager) {
other.Range(func(name1, name2 string, domain ...string) bool {
_ = crm.AddLink(name1, name2, domain...)
return true
})
}
// use this constructor to avoid rebuild of AddMatchingFunc.
func newConditionalRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *ConditionalRoleManager {
rm := NewConditionalRoleManager(maxHierarchyLevel)
rm.matchingFunc = fn
return rm
}
// NewConditionalRoleManager is the constructor for creating an instance of the
// ConditionalRoleManager implementation.
func NewConditionalRoleManager(maxHierarchyLevel int) *ConditionalRoleManager {
rm := ConditionalRoleManager{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
// HasLink determines whether role: name1 inherits role: name2.
func (crm *ConditionalRoleManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
if name1 == name2 || (crm.matchingFunc != nil && crm.Match(name1, name2)) {
return true, nil
}
// Lock to prevent race conditions between getRole and removeRole
crm.mutex.Lock()
defer crm.mutex.Unlock()
user, userCreated := crm.getRole(name1)
role, roleCreated := crm.getRole(name2)
if userCreated {
defer crm.removeRole(user.name)
}
if roleCreated {
defer crm.removeRole(role.name)
}
return crm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, crm.maxHierarchyLevel, domains...), nil
}
// hasLinkHelper use the Breadth First Search algorithm to traverse the Role tree
// Judging whether the user has a role (has link) is to judge whether the role node can be reached from the user node.
func (crm *ConditionalRoleManager) hasLinkHelper(targetName string, roles map[string]*Role, level int, domains ...string) bool {
if level < 0 || len(roles) == 0 {
return false
}
nextRoles := map[string]*Role{}
for _, role := range roles {
if targetName == role.name || (crm.matchingFunc != nil && crm.Match(role.name, targetName)) {
return true
}
role.rangeRoles(func(key, value interface{}) bool {
nextRole := value.(*Role)
return crm.getNextRoles(role, nextRole, domains, nextRoles)
})
}
return crm.hasLinkHelper(targetName, nextRoles, level-1)
}
func (crm *ConditionalRoleManager) getNextRoles(currentRole, nextRole *Role, domains []string, nextRoles map[string]*Role) bool {
passLinkConditionFunc, err := crm.checkLinkCondition(currentRole.name, nextRole.name, domains)
if err != nil {
// Logger has been removed - error is ignored
return false
}
if passLinkConditionFunc {
nextRoles[nextRole.name] = nextRole
}
return true
}
func (crm *ConditionalRoleManager) checkLinkCondition(name1, name2 string, domain []string) (bool, error) {
passLinkConditionFunc := true
var err error
if len(domain) == 0 {
if linkConditionFunc, existLinkCondition := crm.GetLinkConditionFunc(name1, name2); existLinkCondition {
params, _ := crm.GetLinkConditionFuncParams(name1, name2)
passLinkConditionFunc, err = linkConditionFunc(params...)
}
} else {
if linkConditionFunc, existLinkCondition := crm.GetDomainLinkConditionFunc(name1, name2, domain[0]); existLinkCondition {
params, _ := crm.GetLinkConditionFuncParams(name1, name2, domain[0])
passLinkConditionFunc, err = linkConditionFunc(params...)
}
}
return passLinkConditionFunc, err
}
func (crm *ConditionalRoleManager) GetRoles(name string, domains ...string) ([]string, error) {
user, created := crm.getRole(name)
if created {
defer crm.removeRole(user.name)
}
var roles []string
user.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
passLinkConditionFunc, err := crm.checkLinkCondition(name, roleName, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
roles = append(roles, roleName)
}
return true
})
return roles, nil
}
func (crm *ConditionalRoleManager) GetUsers(name string, domains ...string) ([]string, error) {
role, created := crm.getRole(name)
if created {
defer crm.removeRole(name)
}
var users []string
role.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
passLinkConditionFunc, err := crm.checkLinkCondition(userName, name, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
users = append(users, userName)
}
return true
})
return users, nil
}
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) GetImplicitRoles(name string, domain ...string) ([]string, error) {
user, created := crm.getRole(name)
if created {
defer crm.removeRole(user.name)
}
var res []string
roleSet := make(map[string]bool)
roleSet[name] = true
roles := map[string]*Role{user.name: user}
return crm.getImplicitRolesHelper(roles, roleSet, res, 0, domain), nil
}
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) GetImplicitUsers(name string, domain ...string) ([]string, error) {
role, created := crm.getRole(name)
if created {
defer crm.removeRole(role.name)
}
var res []string
userSet := make(map[string]bool)
userSet[name] = true
users := map[string]*Role{role.name: role}
return crm.getImplicitUsersHelper(users, userSet, res, 0, domain), nil
}
// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int, domains []string) []string {
if level >= crm.maxHierarchyLevel || len(roles) == 0 {
return res
}
nextRoles := map[string]*Role{}
for _, role := range roles {
role.rangeRoles(func(key, value interface{}) bool {
roleName := key.(string)
if _, ok := roleSet[roleName]; !ok {
passLinkConditionFunc, err := crm.checkLinkCondition(role.name, roleName, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
res = append(res, roleName)
roleSet[roleName] = true
nextRoles[roleName] = value.(*Role)
}
}
return true
})
}
return crm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1, domains)
}
// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel and link conditions.
func (crm *ConditionalRoleManager) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int, domains []string) []string {
if level >= crm.maxHierarchyLevel || len(users) == 0 {
return res
}
nextUsers := map[string]*Role{}
for _, user := range users {
user.rangeUsers(func(key, value interface{}) bool {
userName := key.(string)
if _, ok := userSet[userName]; !ok {
passLinkConditionFunc, err := crm.checkLinkCondition(userName, user.name, domains)
if err != nil {
// Logger has been removed - error is ignored
return true
}
if passLinkConditionFunc {
res = append(res, userName)
userSet[userName] = true
nextUsers[userName] = value.(*Role)
}
}
return true
})
}
return crm.getImplicitUsersHelper(nextUsers, userSet, res, level+1, domains)
}
// GetLinkConditionFunc get LinkConditionFunc based on userName, roleName.
func (crm *ConditionalRoleManager) GetLinkConditionFunc(userName, roleName string) (rbac.LinkConditionFunc, bool) {
return crm.GetDomainLinkConditionFunc(userName, roleName, defaultDomain)
}
// GetDomainLinkConditionFunc get LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) GetDomainLinkConditionFunc(userName, roleName, domain string) (rbac.LinkConditionFunc, bool) {
user, userCreated := crm.getRole(userName)
role, roleCreated := crm.getRole(roleName)
if userCreated {
crm.removeRole(user.name)
return nil, false
}
if roleCreated {
crm.removeRole(role.name)
return nil, false
}
return user.getLinkConditionFunc(role, domain)
}
// GetLinkConditionFuncParams gets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) GetLinkConditionFuncParams(userName, roleName string, domain ...string) ([]string, bool) {
user, userCreated := crm.getRole(userName)
role, roleCreated := crm.getRole(roleName)
if userCreated {
crm.removeRole(user.name)
return nil, false
}
if roleCreated {
crm.removeRole(role.name)
return nil, false
}
domainName := defaultDomain
if len(domain) != 0 {
domainName = domain[0]
}
if params, ok := user.getLinkConditionFuncParams(role, domainName); ok {
return params, true
} else {
return nil, false
}
}
// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.
func (crm *ConditionalRoleManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {
crm.AddDomainLinkConditionFunc(userName, roleName, defaultDomain, fn)
}
// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.
func (crm *ConditionalRoleManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {
user, _ := crm.getRole(userName)
role, _ := crm.getRole(roleName)
user.addLinkConditionFunc(role, domain, fn)
}
// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {
crm.SetDomainLinkConditionFuncParams(userName, roleName, defaultDomain, params...)
}
// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (crm *ConditionalRoleManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {
user, _ := crm.getRole(userName)
role, _ := crm.getRole(roleName)
user.setLinkConditionFuncParams(role, domain, params...)
}
type ConditionalDomainManager struct {
ConditionalRoleManager
DomainManager
}
// NewConditionalDomainManager is the constructor for creating an instance of the
// ConditionalDomainManager implementation.
func NewConditionalDomainManager(maxHierarchyLevel int) *ConditionalDomainManager {
rm := ConditionalDomainManager{}
_ = rm.Clear() // init allRoles and matchingFuncCache
rm.maxHierarchyLevel = maxHierarchyLevel
return &rm
}
func (cdm *ConditionalDomainManager) load(name interface{}) (value *ConditionalRoleManager, ok bool) {
if r, ok := cdm.rmMap.Load(name); ok {
return r.(*ConditionalRoleManager), true
}
return nil, false
}
// load or create a ConditionalRoleManager instance of domain.
func (cdm *ConditionalDomainManager) getConditionalRoleManager(domain string, store bool) *ConditionalRoleManager {
var rm *ConditionalRoleManager
var ok bool
if rm, ok = cdm.load(domain); !ok {
rm = newConditionalRoleManagerWithMatchingFunc(cdm.maxHierarchyLevel, cdm.matchingFunc)
if store {
cdm.rmMap.Store(domain, rm)
}
if cdm.domainMatchingFunc != nil {
cdm.rmMap.Range(func(key, value interface{}) bool {
domain2 := key.(string)
rm2 := value.(*ConditionalRoleManager)
if domain != domain2 && cdm.Match(domain, domain2) {
rm.copyFrom(rm2)
}
return true
})
}
}
return rm
}
// HasLink determines whether role: name1 inherits role: name2.
func (cdm *ConditionalDomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return false, err
}
rm := cdm.getConditionalRoleManager(domain, false)
return rm.HasLink(name1, name2, domains...)
}
func (cdm *ConditionalDomainManager) GetRoles(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetRoles(name, domains...)
}
func (cdm *ConditionalDomainManager) GetUsers(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetUsers(name, domains...)
}
func (cdm *ConditionalDomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetImplicitRoles(name, domains...)
}
func (cdm *ConditionalDomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {
domain, err := cdm.getDomain(domains...)
if err != nil {
return nil, err
}
crm := cdm.getConditionalRoleManager(domain, false)
return crm.GetImplicitUsers(name, domains...)
}
// AddLink adds the inheritance link between role: name1 and role: name2.
// aka role: name1 inherits role: name2.
func (cdm *ConditionalDomainManager) AddLink(name1 string, name2 string, domains ...string) error {
domain, err := cdm.getDomain(domains...)
if err != nil {
return err
}
conditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist
_ = conditionalRoleManager.AddLink(name1, name2, domain)
cdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.AddLink(name1, name2, domain)
})
return nil
}
// DeleteLink deletes the inheritance link between role: name1 and role: name2.
// aka role: name1 does not inherit role: name2 any more.
func (cdm *ConditionalDomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {
domain, err := cdm.getDomain(domains...)
if err != nil {
return err
}
conditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist
_ = conditionalRoleManager.DeleteLink(name1, name2, domain)
cdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {
_ = rm.DeleteLink(name1, name2, domain)
})
return nil
}
// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.
func (cdm *ConditionalDomainManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddLinkConditionFunc(userName, roleName, fn)
return true
})
}
// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.
func (cdm *ConditionalDomainManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddDomainLinkConditionFunc(userName, roleName, domain, fn)
return true
})
}
// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName.
func (cdm *ConditionalDomainManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).SetLinkConditionFuncParams(userName, roleName, params...)
return true
})
}
// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.
func (cdm *ConditionalDomainManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).SetDomainLinkConditionFuncParams(userName, roleName, domain, params...)
return true
})
}
// AddDomainMatchingFunc support use domain pattern in g.
func (cdm *ConditionalDomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {
cdm.domainMatchingFunc = fn
cdm.rmMap.Range(func(key, value interface{}) bool {
value.(*ConditionalRoleManager).AddDomainMatchingFunc(name, fn)
return true
})
cdm.rebuild()
}
// rebuild clears the map of ConditionalRoleManagers.
func (cdm *ConditionalDomainManager) rebuild() {
rmMap := cdm.rmMap
_ = cdm.Clear()
rmMap.Range(func(key, value interface{}) bool {
domain := key.(string)
crm := value.(*ConditionalRoleManager)
crm.Range(func(name1, name2 string, _ ...string) bool {
_ = cdm.AddLink(name1, name2, domain)
return true
})
return true
})
}
================================================
FILE: rbac/default-role-manager/role_manager_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package defaultrolemanager
import (
"fmt"
"sync"
"sync/atomic"
"testing"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/util"
)
func testRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, res bool) {
t.Helper()
myRes, _ := rm.HasLink(name1, name2)
t.Logf("%s, %s: %t", name1, name2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", name1, name2, !res, res)
}
}
func testDomainRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, domain string, res bool) {
t.Helper()
myRes, _ := rm.HasLink(name1, name2, domain)
t.Logf("%s :: %s, %s: %t", domain, name1, name2, myRes)
if myRes != res {
t.Errorf("%s :: %s < %s: %t, supposed to be %t", domain, name1, name2, !res, res)
}
}
func testPrintRoles(t *testing.T, rm rbac.RoleManager, name string, res []string) {
t.Helper()
myRes, _ := rm.GetRoles(name)
t.Logf("%s: %s", name, myRes)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func testPrintUsers(t *testing.T, rm rbac.RoleManager, name string, res []string) {
t.Helper()
myRes, _ := rm.GetUsers(name)
t.Logf("%s: %s", name, myRes)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func testPrintRolesWithDomain(t *testing.T, rm rbac.RoleManager, name string, domain string, res []string) {
t.Helper()
myRes, _ := rm.GetRoles(name, domain)
if !util.SetEquals(myRes, res) {
t.Errorf("%s: %s, supposed to be %s", name, myRes, res)
}
}
func TestRole(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u2", "g1")
_ = rm.AddLink("u3", "g2")
_ = rm.AddLink("u4", "g2")
_ = rm.AddLink("u4", "g3")
_ = rm.AddLink("g1", "g3")
// Current role inheritance tree:
// g3 g2
// / \ / \
// g1 u4 u3
// / \
// u1 u2
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", true)
testRole(t, rm, "u2", "g1", true)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", true)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", true)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", true)
testRole(t, rm, "u4", "g3", true)
testPrintRoles(t, rm, "u1", []string{"g1"})
testPrintRoles(t, rm, "u2", []string{"g1"})
testPrintRoles(t, rm, "u3", []string{"g2"})
testPrintRoles(t, rm, "u4", []string{"g2", "g3"})
testPrintRoles(t, rm, "g1", []string{"g3"})
testPrintRoles(t, rm, "g2", []string{})
testPrintRoles(t, rm, "g3", []string{})
_ = rm.DeleteLink("g1", "g3")
_ = rm.DeleteLink("u4", "g2")
// Current role inheritance tree after deleting the links:
// g3 g2
// \ \
// g1 u4 u3
// / \
// u1 u2
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", false)
testRole(t, rm, "u2", "g1", true)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", false)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", true)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", false)
testRole(t, rm, "u4", "g3", true)
testPrintRoles(t, rm, "u1", []string{"g1"})
testPrintRoles(t, rm, "u2", []string{"g1"})
testPrintRoles(t, rm, "u3", []string{"g2"})
testPrintRoles(t, rm, "u4", []string{"g3"})
testPrintRoles(t, rm, "g1", []string{})
testPrintRoles(t, rm, "g2", []string{})
testPrintRoles(t, rm, "g3", []string{})
rm = NewRoleManager(3)
rm.AddMatchingFunc("keyMatch", util.KeyMatch)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u1", "*")
_ = rm.AddLink("u2", "g2")
// Current role inheritance tree after deleting the links:
// g1 g2
// \ / \
// * u2
// |
// u1
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", true)
testRole(t, rm, "u2", "g2", true)
testRole(t, rm, "u2", "g1", false)
testPrintRoles(t, rm, "u1", []string{"*", "u1", "u2", "g1", "g2"})
testPrintUsers(t, rm, "*", []string{"u1"})
}
func TestDomainRole(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1", "domain1")
_ = rm.AddLink("u2", "g1", "domain1")
_ = rm.AddLink("u3", "admin", "domain2")
_ = rm.AddLink("u4", "admin", "domain2")
_ = rm.AddLink("u4", "admin", "domain1")
_ = rm.AddLink("g1", "admin", "domain1")
// Current role inheritance tree:
// domain1:admin domain2:admin
// / \ / \
// domain1:g1 u4 u3
// / \
// u1 u2
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u1", "g1", "domain2", false)
testDomainRole(t, rm, "u1", "admin", "domain1", true)
testDomainRole(t, rm, "u1", "admin", "domain2", false)
testDomainRole(t, rm, "u2", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain2", false)
testDomainRole(t, rm, "u2", "admin", "domain1", true)
testDomainRole(t, rm, "u2", "admin", "domain2", false)
testDomainRole(t, rm, "u3", "g1", "domain1", false)
testDomainRole(t, rm, "u3", "g1", "domain2", false)
testDomainRole(t, rm, "u3", "admin", "domain1", false)
testDomainRole(t, rm, "u3", "admin", "domain2", true)
testDomainRole(t, rm, "u4", "g1", "domain1", false)
testDomainRole(t, rm, "u4", "g1", "domain2", false)
testDomainRole(t, rm, "u4", "admin", "domain1", true)
testDomainRole(t, rm, "u4", "admin", "domain2", true)
_ = rm.DeleteLink("g1", "admin", "domain1")
_ = rm.DeleteLink("u4", "admin", "domain2")
// Current role inheritance tree after deleting the links:
// domain1:admin domain2:admin
// \ \
// domain1:g1 u4 u3
// / \
// u1 u2
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u1", "g1", "domain2", false)
testDomainRole(t, rm, "u1", "admin", "domain1", false)
testDomainRole(t, rm, "u1", "admin", "domain2", false)
testDomainRole(t, rm, "u2", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain2", false)
testDomainRole(t, rm, "u2", "admin", "domain1", false)
testDomainRole(t, rm, "u2", "admin", "domain2", false)
testDomainRole(t, rm, "u3", "g1", "domain1", false)
testDomainRole(t, rm, "u3", "g1", "domain2", false)
testDomainRole(t, rm, "u3", "admin", "domain1", false)
testDomainRole(t, rm, "u3", "admin", "domain2", true)
testDomainRole(t, rm, "u4", "g1", "domain1", false)
testDomainRole(t, rm, "u4", "g1", "domain2", false)
testDomainRole(t, rm, "u4", "admin", "domain1", true)
testDomainRole(t, rm, "u4", "admin", "domain2", false)
}
func TestClear(t *testing.T) {
rm := NewRoleManager(3)
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("u2", "g1")
_ = rm.AddLink("u3", "g2")
_ = rm.AddLink("u4", "g2")
_ = rm.AddLink("u4", "g3")
_ = rm.AddLink("g1", "g3")
// Current role inheritance tree:
// g3 g2
// / \ / \
// g1 u4 u3
// / \
// u1 u2
_ = rm.Clear()
// All data is cleared.
// No role inheritance now.
testRole(t, rm, "u1", "g1", false)
testRole(t, rm, "u1", "g2", false)
testRole(t, rm, "u1", "g3", false)
testRole(t, rm, "u2", "g1", false)
testRole(t, rm, "u2", "g2", false)
testRole(t, rm, "u2", "g3", false)
testRole(t, rm, "u3", "g1", false)
testRole(t, rm, "u3", "g2", false)
testRole(t, rm, "u3", "g3", false)
testRole(t, rm, "u4", "g1", false)
testRole(t, rm, "u4", "g2", false)
testRole(t, rm, "u4", "g3", false)
}
func TestDomainPatternRole(t *testing.T) {
rm := NewRoleManager(10)
rm.AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("u1", "g1", "domain1")
_ = rm.AddLink("u2", "g1", "domain2")
_ = rm.AddLink("u3", "g1", "*")
_ = rm.AddLink("u4", "g2", "domain3")
// Current role inheritance tree after deleting the links:
// domain1:g1 domain2:g1 domain3:g2
// / \ / \ |
// domain1:u1 *:g1 domain2:u2 domain3:u4
// |
// *:u3
testDomainRole(t, rm, "u1", "g1", "domain1", true)
testDomainRole(t, rm, "u2", "g1", "domain1", false)
testDomainRole(t, rm, "u2", "g1", "domain2", true)
testDomainRole(t, rm, "u3", "g1", "domain1", true)
testDomainRole(t, rm, "u3", "g1", "domain2", true)
testDomainRole(t, rm, "u1", "g2", "domain1", false)
testDomainRole(t, rm, "u4", "g2", "domain3", true)
testDomainRole(t, rm, "u3", "g2", "domain3", false)
testPrintRolesWithDomain(t, rm, "u3", "domain1", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u1", "domain1", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u3", "domain2", []string{"g1"})
testPrintRolesWithDomain(t, rm, "u1", "domain2", []string{})
testPrintRolesWithDomain(t, rm, "u4", "domain3", []string{"g2"})
}
func TestAllMatchingFunc(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("keyMatch2", util.KeyMatch2)
rm.AddDomainMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("/book/:id", "book_group", "*")
// Current role inheritance tree after deleting the links:
// *:book_group
// |
// *:/book/:id
testDomainRole(t, rm, "/book/1", "book_group", "domain1", true)
testDomainRole(t, rm, "/book/2", "book_group", "domain1", true)
}
func TestMatchingFuncOrder(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("regexMatch", util.RegexMatch)
_ = rm.AddLink("g\\d+", "root")
_ = rm.AddLink("u1", "g1")
testRole(t, rm, "u1", "root", true)
_ = rm.Clear()
_ = rm.AddLink("u1", "g1")
_ = rm.AddLink("g\\d+", "root")
testRole(t, rm, "u1", "root", true)
_ = rm.Clear()
_ = rm.AddLink("u1", "g\\d+")
testRole(t, rm, "u1", "g1", true)
testRole(t, rm, "u1", "g2", true)
}
func TestDomainMatchingFuncWithDifferentDomain(t *testing.T) {
rm := NewRoleManager(10)
rm.AddDomainMatchingFunc("keyMatch", util.KeyMatch)
_ = rm.AddLink("alice", "editor", "*")
_ = rm.AddLink("editor", "admin", "domain1")
testDomainRole(t, rm, "alice", "admin", "domain1", true)
testDomainRole(t, rm, "alice", "admin", "domain2", false)
}
func TestTemporaryRoles(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("regexMatch", util.RegexMatch)
_ = rm.AddLink("u\\d+", "user")
for i := 0; i < 10; i++ {
testRole(t, rm, fmt.Sprintf("u%d", i), "user", true)
}
testPrintUsers(t, rm, "user", []string{"u\\d+"})
testPrintRoles(t, rm, "u1", []string{"user"})
_ = rm.AddLink("u1", "manager")
for i := 10; i < 20; i++ {
testRole(t, rm, fmt.Sprintf("u%d", i), "user", true)
}
testPrintUsers(t, rm, "user", []string{"u\\d+", "u1"})
testPrintRoles(t, rm, "u1", []string{"user", "manager"})
}
func TestMaxHierarchyLevel(t *testing.T) {
rm := NewRoleManager(1)
_ = rm.AddLink("level0", "level1")
_ = rm.AddLink("level1", "level2")
_ = rm.AddLink("level2", "level3")
testRole(t, rm, "level0", "level0", true)
testRole(t, rm, "level0", "level1", true)
testRole(t, rm, "level0", "level2", false)
testRole(t, rm, "level0", "level3", false)
testRole(t, rm, "level1", "level2", true)
testRole(t, rm, "level1", "level3", false)
rm = NewRoleManager(2)
_ = rm.AddLink("level0", "level1")
_ = rm.AddLink("level1", "level2")
_ = rm.AddLink("level2", "level3")
testRole(t, rm, "level0", "level0", true)
testRole(t, rm, "level0", "level1", true)
testRole(t, rm, "level0", "level2", true)
testRole(t, rm, "level0", "level3", false)
testRole(t, rm, "level1", "level2", true)
testRole(t, rm, "level1", "level3", true)
}
// TestConcurrentHasLink tests concurrent HasLink calls for race conditions.
// This test verifies that concurrent HasLink calls with matching functions
// don't produce inconsistent results due to temporary role creation/deletion races.
// Regression test for issue #1318.
func TestConcurrentHasLink(t *testing.T) {
rm := NewRoleManager(10)
rm.AddMatchingFunc("keyMatch2", util.KeyMatch2)
_ = rm.AddLink("alice", "admin")
_ = rm.AddLink("admin", "/data/*")
expected, _ := rm.HasLink("alice", "/data/123")
const numGoroutines = 20
const numIterations = 100
var inconsistencies int64
var wg sync.WaitGroup
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < numIterations; j++ {
result, err := rm.HasLink("alice", "/data/123")
if err != nil {
t.Errorf("HasLink failed: %v", err)
return
} else if result != expected {
atomic.AddInt64(&inconsistencies, 1)
}
}
}()
}
wg.Wait()
if inconsistencies > 0 {
t.Errorf("Found %d inconsistencies in %d total operations",
inconsistencies, numGoroutines*numIterations)
}
}
================================================
FILE: rbac/role_manager.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rbac
type MatchingFunc func(arg1 string, arg2 string) bool
type LinkConditionFunc = func(args ...string) (bool, error)
// RoleManager provides interface to define the operations for managing roles.
type RoleManager interface {
// Clear clears all stored data and resets the role manager to the initial state.
Clear() error
// AddLink adds the inheritance link between two roles. role: name1 and role: name2.
// domain is a prefix to the roles (can be used for other purposes).
AddLink(name1 string, name2 string, domain ...string) error
// Deprecated: BuildRelationship is no longer required
BuildRelationship(name1 string, name2 string, domain ...string) error
// DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
// domain is a prefix to the roles (can be used for other purposes).
DeleteLink(name1 string, name2 string, domain ...string) error
// HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.
// domain is a prefix to the roles (can be used for other purposes).
HasLink(name1 string, name2 string, domain ...string) (bool, error)
// GetRoles gets the roles that a user inherits.
// domain is a prefix to the roles (can be used for other purposes).
GetRoles(name string, domain ...string) ([]string, error)
// GetUsers gets the users that inherits a role.
// domain is a prefix to the users (can be used for other purposes).
GetUsers(name string, domain ...string) ([]string, error)
// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.
// domain is a prefix to the roles (can be used for other purposes).
GetImplicitRoles(name string, domain ...string) ([]string, error)
// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.
// domain is a prefix to the users (can be used for other purposes).
GetImplicitUsers(name string, domain ...string) ([]string, error)
// GetDomains gets domains that a user has
GetDomains(name string) ([]string, error)
// GetAllDomains gets all domains
GetAllDomains() ([]string, error)
// PrintRoles prints all the roles to log.
PrintRoles() error
// Match matches the domain with the pattern
Match(str string, pattern string) bool
// AddMatchingFunc adds the matching function
AddMatchingFunc(name string, fn MatchingFunc)
// AddDomainMatchingFunc adds the domain matching function
AddDomainMatchingFunc(name string, fn MatchingFunc)
// DeleteDomain deletes all data of a domain in the role manager.
DeleteDomain(domain string) error
}
// ConditionalRoleManager provides interface to define the operations for managing roles.
// Link with conditions is supported.
type ConditionalRoleManager interface {
RoleManager
// AddLinkConditionFunc Add condition function fn for Link userName->roleName,
// when fn returns true, Link is valid, otherwise invalid
AddLinkConditionFunc(userName, roleName string, fn LinkConditionFunc)
// SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName
SetLinkConditionFuncParams(userName, roleName string, params ...string)
// AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},
// when fn returns true, Link is valid, otherwise invalid
AddDomainLinkConditionFunc(user string, role string, domain string, fn LinkConditionFunc)
// SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn
// for Link userName->{roleName, domain}
SetDomainLinkConditionFuncParams(user string, role string, domain string, params ...string)
}
================================================
FILE: rbac_api.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"strings"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/util"
)
// GetRolesForUser gets the roles that a user has.
func (e *Enforcer) GetRolesForUser(name string, domain ...string) ([]string, error) {
rm := e.GetRoleManager()
if rm == nil {
return nil, fmt.Errorf("role manager is not initialized")
}
res, err := rm.GetRoles(name, domain...)
return res, err
}
// GetUsersForRole gets the users that has a role.
func (e *Enforcer) GetUsersForRole(name string, domain ...string) ([]string, error) {
rm := e.GetRoleManager()
if rm == nil {
return nil, fmt.Errorf("role manager is not initialized")
}
res, err := rm.GetUsers(name, domain...)
return res, err
}
// HasRoleForUser determines whether a user has a role.
func (e *Enforcer) HasRoleForUser(name string, role string, domain ...string) (bool, error) {
roles, err := e.GetRolesForUser(name, domain...)
if err != nil {
return false, err
}
hasRole := false
for _, r := range roles {
if r == role {
hasRole = true
break
}
}
return hasRole, nil
}
// AddRoleForUser adds a role for a user.
// Returns false if the user already has the role (aka not affected).
func (e *Enforcer) AddRoleForUser(user string, role string, domain ...string) (bool, error) {
args := []string{user, role}
args = append(args, domain...)
return e.AddGroupingPolicy(args)
}
// AddRolesForUser adds roles for a user.
// Returns false if the user already has the roles (aka not affected).
func (e *Enforcer) AddRolesForUser(user string, roles []string, domain ...string) (bool, error) {
var rules [][]string
for _, role := range roles {
rule := []string{user, role}
rule = append(rule, domain...)
rules = append(rules, rule)
}
return e.AddGroupingPolicies(rules)
}
// DeleteRoleForUser deletes a role for a user.
// Returns false if the user does not have the role (aka not affected).
func (e *Enforcer) DeleteRoleForUser(user string, role string, domain ...string) (bool, error) {
args := []string{user, role}
args = append(args, domain...)
return e.RemoveGroupingPolicy(args)
}
// DeleteRolesForUser deletes all roles for a user.
// Returns false if the user does not have any roles (aka not affected).
func (e *Enforcer) DeleteRolesForUser(user string, domain ...string) (bool, error) {
var args []string
if len(domain) == 0 {
args = []string{user}
} else if len(domain) > 1 {
return false, errors.ErrDomainParameter
} else {
args = []string{user, "", domain[0]}
}
return e.RemoveFilteredGroupingPolicy(0, args...)
}
// DeleteUser deletes a user.
// Returns false if the user does not exist (aka not affected).
func (e *Enforcer) DeleteUser(user string) (bool, error) {
var err error
res1, err := e.RemoveFilteredGroupingPolicy(0, user)
if err != nil {
return res1, err
}
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
res2, err := e.RemoveFilteredPolicy(subIndex, user)
return res1 || res2, err
}
// DeleteRole deletes a role.
// Returns false if the role does not exist (aka not affected).
func (e *Enforcer) DeleteRole(role string) (bool, error) {
var err error
res1, err := e.RemoveFilteredGroupingPolicy(0, role)
if err != nil {
return res1, err
}
res2, err := e.RemoveFilteredGroupingPolicy(1, role)
if err != nil {
return res1, err
}
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
res3, err := e.RemoveFilteredPolicy(subIndex, role)
return res1 || res2 || res3, err
}
// DeletePermission deletes a permission.
// Returns false if the permission does not exist (aka not affected).
func (e *Enforcer) DeletePermission(permission ...string) (bool, error) {
return e.RemoveFilteredPolicy(1, permission...)
}
// AddPermissionForUser adds a permission for a user or role.
// Returns false if the user or role already has the permission (aka not affected).
func (e *Enforcer) AddPermissionForUser(user string, permission ...string) (bool, error) {
return e.AddPolicy(util.JoinSlice(user, permission...))
}
// AddPermissionsForUser adds multiple permissions for a user or role.
// Returns false if the user or role already has one of the permissions (aka not affected).
func (e *Enforcer) AddPermissionsForUser(user string, permissions ...[]string) (bool, error) {
var rules [][]string
for _, permission := range permissions {
rules = append(rules, util.JoinSlice(user, permission...))
}
return e.AddPolicies(rules)
}
// DeletePermissionForUser deletes a permission for a user or role.
// Returns false if the user or role does not have the permission (aka not affected).
func (e *Enforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) {
return e.RemovePolicy(util.JoinSlice(user, permission...))
}
// DeletePermissionsForUser deletes permissions for a user or role.
// Returns false if the user or role does not have any permissions (aka not affected).
func (e *Enforcer) DeletePermissionsForUser(user string) (bool, error) {
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
return e.RemoveFilteredPolicy(subIndex, user)
}
// GetPermissionsForUser gets permissions for a user or role.
func (e *Enforcer) GetPermissionsForUser(user string, domain ...string) ([][]string, error) {
return e.GetNamedPermissionsForUser("p", user, domain...)
}
// GetNamedPermissionsForUser gets permissions for a user or role by named policy.
func (e *Enforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {
permission := make([][]string, 0)
for pType, assertion := range e.model["p"] {
if pType != ptype {
continue
}
args := make([]string, len(assertion.Tokens))
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
subIndex = 0
}
args[subIndex] = user
if len(domain) > 0 {
var index int
index, err = e.GetFieldIndex(ptype, constant.DomainIndex)
if err != nil {
return permission, err
}
args[index] = domain[0]
}
perm, err := e.GetFilteredNamedPolicy(ptype, 0, args...)
if err != nil {
return permission, err
}
permission = append(permission, perm...)
}
return permission, nil
}
// HasPermissionForUser determines whether a user has a permission.
func (e *Enforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {
return e.HasPolicy(util.JoinSlice(user, permission...))
}
// GetImplicitRolesForUser gets implicit roles that a user has.
// Compared to GetRolesForUser(), this function retrieves indirect roles besides direct roles.
// For example:
// g, alice, role:admin
// g, role:admin, role:user
//
// GetRolesForUser("alice") can only get: ["role:admin"].
// But GetImplicitRolesForUser("alice") will get: ["role:admin", "role:user"].
func (e *Enforcer) GetImplicitRolesForUser(name string, domain ...string) ([]string, error) {
var res []string
for rm := range e.rmMap {
roles, err := e.GetNamedImplicitRolesForUser(rm, name, domain...)
if err != nil {
return nil, err
}
res = append(res, roles...)
}
for crm := range e.condRmMap {
roles, err := e.GetNamedImplicitRolesForUser(crm, name, domain...)
if err != nil {
return nil, err
}
res = append(res, roles...)
}
return res, nil
}
// GetNamedImplicitRolesForUser gets implicit roles that a user has by named role definition.
// Compared to GetImplicitRolesForUser(), this function retrieves indirect roles besides direct roles.
// For example:
// g, alice, role:admin
// g, role:admin, role:user
// g2, alice, role:admin2
//
// GetImplicitRolesForUser("alice") can only get: ["role:admin", "role:user"].
// But GetNamedImplicitRolesForUser("g2", "alice") will get: ["role:admin2"].
func (e *Enforcer) GetNamedImplicitRolesForUser(ptype string, name string, domain ...string) ([]string, error) {
rm := e.GetNamedRoleManager(ptype)
if rm == nil {
return nil, fmt.Errorf("role manager %s is not initialized", ptype)
}
// Use the role manager's GetImplicitRoles method which respects maxHierarchyLevel
return rm.GetImplicitRoles(name, domain...)
}
// GetImplicitUsersForRole gets implicit users for a role.
func (e *Enforcer) GetImplicitUsersForRole(name string, domain ...string) ([]string, error) {
res := []string{}
var rms []rbac.RoleManager
for _, rm := range e.rmMap {
rms = append(rms, rm)
}
for _, crm := range e.condRmMap {
rms = append(rms, crm)
}
for _, rm := range rms {
// Use the role manager's GetImplicitUsers method which respects maxHierarchyLevel
users, err := rm.GetImplicitUsers(name, domain...)
if err != nil && err.Error() != "error: name does not exist" {
return nil, err
}
res = append(res, users...)
}
return res, nil
}
// GetImplicitPermissionsForUser gets implicit permissions for a user or role.
// Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles.
// For example:
// p, admin, data1, read
// p, alice, data2, read
// g, alice, admin
//
// GetPermissionsForUser("alice") can only get: [["alice", "data2", "read"]].
// But GetImplicitPermissionsForUser("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]].
func (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) {
return e.GetNamedImplicitPermissionsForUser("p", "g", user, domain...)
}
// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.
// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.
// For example:
// p, admin, data1, read
// p2, admin, create
// g, alice, admin
//
// GetImplicitPermissionsForUser("alice") can only get: [["admin", "data1", "read"]], whose policy is default policy "p"
// But you can specify the named policy "p2" to get: [["admin", "create"]] by GetNamedImplicitPermissionsForUser("p2","alice").
func (e *Enforcer) GetNamedImplicitPermissionsForUser(ptype string, gtype string, user string, domain ...string) ([][]string, error) {
permission := make([][]string, 0)
rm := e.GetNamedRoleManager(gtype)
if rm == nil {
return nil, fmt.Errorf("role manager %s is not initialized", gtype)
}
roles, err := e.GetNamedImplicitRolesForUser(gtype, user, domain...)
if err != nil {
return nil, err
}
policyRoles := make(map[string]struct{}, len(roles)+1)
policyRoles[user] = struct{}{}
for _, r := range roles {
policyRoles[r] = struct{}{}
}
domainIndex, err := e.GetFieldIndex(ptype, constant.DomainIndex)
for _, rule := range e.model["p"][ptype].Policy {
if len(domain) == 0 {
if _, ok := policyRoles[rule[0]]; ok {
permission = append(permission, deepCopyPolicy(rule))
}
continue
}
if len(domain) > 1 {
return nil, errors.ErrDomainParameter
}
if err != nil {
return nil, err
}
d := domain[0]
matched := rm.Match(d, rule[domainIndex])
if !matched {
continue
}
if _, ok := policyRoles[rule[0]]; ok {
newRule := deepCopyPolicy(rule)
newRule[domainIndex] = d
permission = append(permission, newRule)
}
}
return permission, nil
}
// GetImplicitUsersForPermission gets implicit users for a permission.
// For example:
// p, admin, data1, read
// p, bob, data1, read
// g, alice, admin
//
// GetImplicitUsersForPermission("data1", "read") will get: ["alice", "bob"].
// Note: only users will be returned, roles (2nd arg in "g") will be excluded.
func (e *Enforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) {
pSubjects, err := e.GetAllSubjects()
if err != nil {
return nil, err
}
gInherit, err := e.model.GetValuesForFieldInPolicyAllTypes("g", 1)
if err != nil {
return nil, err
}
gSubjects, err := e.model.GetValuesForFieldInPolicyAllTypes("g", 0)
if err != nil {
return nil, err
}
subjects := append(pSubjects, gSubjects...)
util.ArrayRemoveDuplicates(&subjects)
subjects = util.SetSubtract(subjects, gInherit)
res := []string{}
for _, user := range subjects {
req := util.JoinSliceAny(user, permission...)
allowed, err := e.Enforce(req...)
if err != nil {
return nil, err
}
if allowed {
res = append(res, user)
}
}
return res, nil
}
// GetDomainsForUser gets all domains.
func (e *Enforcer) GetDomainsForUser(user string) ([]string, error) {
var domains []string
for _, rm := range e.rmMap {
domain, err := rm.GetDomains(user)
if err != nil {
return nil, err
}
domains = append(domains, domain...)
}
for _, crm := range e.condRmMap {
domain, err := crm.GetDomains(user)
if err != nil {
return nil, err
}
domains = append(domains, domain...)
}
return domains, nil
}
// GetImplicitResourcesForUser returns all policies that user obtaining in domain.
func (e *Enforcer) GetImplicitResourcesForUser(user string, domain ...string) ([][]string, error) {
permissions, err := e.GetImplicitPermissionsForUser(user, domain...)
if err != nil {
return nil, err
}
res := make([][]string, 0)
for _, permission := range permissions {
if permission[0] == user {
res = append(res, permission)
continue
}
resLocal := [][]string{{user}}
tokensLength := len(permission)
t := make([][]string, 1, tokensLength)
for _, token := range permission[1:] {
tokens, err := e.GetImplicitUsersForRole(token, domain...)
if err != nil {
return nil, err
}
tokens = append(tokens, token)
t = append(t, tokens)
}
for i := 1; i < tokensLength; i++ {
n := make([][]string, 0)
for _, tokens := range t[i] {
for _, policy := range resLocal {
t := append([]string(nil), policy...)
t = append(t, tokens)
n = append(n, t)
}
}
resLocal = n
}
res = append(res, resLocal...)
}
return res, nil
}
// deepCopyPolicy returns a deepcopy version of the policy to prevent changing policies through returned slice.
func deepCopyPolicy(src []string) []string {
newRule := make([]string, len(src))
copy(newRule, src)
return newRule
}
// GetAllowedObjectConditions returns a string array of object conditions that the user can access.
// For example: conditions, err := e.GetAllowedObjectConditions("alice", "read", "r.obj.")
// Note:
//
// 0. prefix: You can customize the prefix of the object conditions, and "r.obj." is commonly used as a prefix.
// After removing the prefix, the remaining part is the condition of the object.
// If there is an obj policy that does not meet the prefix requirement, an errors.ERR_OBJ_CONDITION will be returned.
//
// 1. If the 'objectConditions' array is empty, return errors.ERR_EMPTY_CONDITION
// This error is returned because some data adapters' ORM return full table data by default
// when they receive an empty condition, which tends to behave contrary to expectations.(e.g. GORM)
// If you are using an adapter that does not behave like this, you can choose to ignore this error.
func (e *Enforcer) GetAllowedObjectConditions(user string, action string, prefix string) ([]string, error) {
permissions, err := e.GetImplicitPermissionsForUser(user)
if err != nil {
return nil, err
}
var objectConditions []string
for _, policy := range permissions {
// policy {sub, obj, act}
if policy[2] == action {
if !strings.HasPrefix(policy[1], prefix) {
return nil, errors.ErrObjCondition
}
objectConditions = append(objectConditions, strings.TrimPrefix(policy[1], prefix))
}
}
if len(objectConditions) == 0 {
return nil, errors.ErrEmptyCondition
}
return objectConditions, nil
}
// removeDuplicatePermissions Convert permissions to string as a hash to deduplicate.
func removeDuplicatePermissions(permissions [][]string) [][]string {
permissionsSet := make(map[string]bool)
res := make([][]string, 0)
for _, permission := range permissions {
permissionStr := util.ArrayToString(permission)
if permissionsSet[permissionStr] {
continue
}
permissionsSet[permissionStr] = true
res = append(res, permission)
}
return res
}
// GetImplicitUsersForResource return implicit user based on resource.
// for example:
// p, alice, data1, read
// p, bob, data2, write
// p, data2_admin, data2, read
// p, data2_admin, data2, write
// g, alice, data2_admin
// GetImplicitUsersForResource("data2") will return [[bob data2 write] [alice data2 read] [alice data2 write]]
// GetImplicitUsersForResource("data1") will return [[alice data1 read]]
// Note: only users will be returned, roles (2nd arg in "g") will be excluded.
func (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, error) {
return e.GetNamedImplicitUsersForResource("g", resource)
}
// GetNamedImplicitUsersForResource return implicit user based on resource with named policy support.
// This function handles resource role relationships through named policies (e.g., g2, g3, etc.).
// for example:
// p, admin_group, admin_data, *
// g, admin, admin_group
// g2, app, admin_data
// GetNamedImplicitUsersForResource("g2", "app") will return users who have access to admin_data through g2 relationship.
func (e *Enforcer) GetNamedImplicitUsersForResource(ptype string, resource string) ([][]string, error) {
permissions := make([][]string, 0)
subjectIndex, _ := e.GetFieldIndex("p", "sub")
objectIndex, _ := e.GetFieldIndex("p", "obj")
rm := e.GetRoleManager()
if rm == nil {
return nil, fmt.Errorf("role manager is not initialized")
}
isRole := make(map[string]bool)
roles, err := e.GetAllRoles()
if err != nil {
return nil, err
}
for _, role := range roles {
isRole[role] = true
}
// Get all resource types that the resource can access through ptype (e.g., g2)
ptypePolicies, _ := e.GetNamedGroupingPolicy(ptype)
resourceAccessibleResourceTypes := make(map[string]bool)
for _, ptypePolicy := range ptypePolicies {
if ptypePolicy[0] == resource { // ptypePolicy[0] is the resource
resourceAccessibleResourceTypes[ptypePolicy[1]] = true // ptypePolicy[1] is the resource type it can access
}
}
for _, rule := range e.model["p"]["p"].Policy {
obj := rule[objectIndex]
sub := rule[subjectIndex]
// Check if this policy is directly for the resource OR for a resource type the resource can access
if obj == resource || resourceAccessibleResourceTypes[obj] {
if !isRole[sub] {
permissions = append(permissions, rule)
} else {
users, err := rm.GetUsers(sub)
if err != nil {
continue
}
for _, user := range users {
implicitUserRule := deepCopyPolicy(rule)
implicitUserRule[subjectIndex] = user
permissions = append(permissions, implicitUserRule)
}
}
}
}
res := removeDuplicatePermissions(permissions)
return res, nil
}
// GetImplicitUsersForResourceByDomain return implicit user based on resource and domain.
// Compared to GetImplicitUsersForResource, domain is supported.
func (e *Enforcer) GetImplicitUsersForResourceByDomain(resource string, domain string) ([][]string, error) {
permissions := make([][]string, 0)
subjectIndex, _ := e.GetFieldIndex("p", "sub")
objectIndex, _ := e.GetFieldIndex("p", "obj")
domIndex, _ := e.GetFieldIndex("p", "dom")
rm := e.GetRoleManager()
if rm == nil {
return nil, fmt.Errorf("role manager is not initialized")
}
isRole := make(map[string]bool)
if roles, err := e.GetAllRolesByDomain(domain); err != nil {
return nil, err
} else {
for _, role := range roles {
isRole[role] = true
}
}
for _, rule := range e.model["p"]["p"].Policy {
obj := rule[objectIndex]
if obj != resource {
continue
}
sub := rule[subjectIndex]
if !isRole[sub] {
permissions = append(permissions, rule)
} else {
if domain != rule[domIndex] {
continue
}
users, err := rm.GetUsers(sub, domain)
if err != nil {
return nil, err
}
for _, user := range users {
implicitUserRule := deepCopyPolicy(rule)
implicitUserRule[subjectIndex] = user
permissions = append(permissions, implicitUserRule)
}
}
}
res := removeDuplicatePermissions(permissions)
return res, nil
}
// GetImplicitObjectPatternsForUser returns all object patterns (with wildcards) that a user has for a given domain and action.
// For example:
// p, admin, chronicle/123, location/*, read
// p, user, chronicle/456, location/789, read
// g, alice, admin
// g, bob, user
//
// GetImplicitObjectPatternsForUser("alice", "chronicle/123", "read") will return ["location/*"].
// GetImplicitObjectPatternsForUser("bob", "chronicle/456", "read") will return ["location/789"].
func (e *Enforcer) GetImplicitObjectPatternsForUser(user string, domain string, action string) ([]string, error) {
roles, err := e.GetImplicitRolesForUser(user, domain)
if err != nil {
return nil, err
}
subjects := append([]string{user}, roles...)
subjectIndex, _ := e.GetFieldIndex("p", constant.SubjectIndex)
domainIndex, _ := e.GetFieldIndex("p", constant.DomainIndex)
objectIndex, _ := e.GetFieldIndex("p", constant.ObjectIndex)
actionIndex, _ := e.GetFieldIndex("p", constant.ActionIndex)
patterns := make(map[string]struct{})
for _, rule := range e.model["p"]["p"].Policy {
sub := rule[subjectIndex]
matched := false
for _, subject := range subjects {
if sub == subject {
matched = true
break
}
}
if !matched {
continue
}
if !e.matchDomain(domainIndex, domain, rule) {
continue
}
ruleAction := rule[actionIndex]
if ruleAction != action && ruleAction != "*" {
continue
}
obj := rule[objectIndex]
patterns[obj] = struct{}{}
}
result := make([]string, 0, len(patterns))
for pattern := range patterns {
result = append(result, pattern)
}
return result, nil
}
// matchDomain checks if the domain matches the rule domain using pattern matching.
func (e *Enforcer) matchDomain(domainIndex int, domain string, rule []string) bool {
if domainIndex < 0 || domain == "" {
return true
}
ruleDomain := rule[domainIndex]
if ruleDomain == domain {
return true
}
for _, rm := range e.rmMap {
if rm.Match(domain, ruleDomain) {
return true
}
}
for _, crm := range e.condRmMap {
if crm.Match(domain, ruleDomain) {
return true
}
}
return false
}
================================================
FILE: rbac_api_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// rbac_api_context.go
package casbin
import (
"context"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/errors"
"github.com/casbin/casbin/v3/util"
)
// AddRoleForUserCtx adds a role for a user with context support.
// Returns false if the user already has the role (aka not affected).
func (e *ContextEnforcer) AddRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error) {
args := []string{user, role}
args = append(args, domain...)
return e.AddGroupingPolicyCtx(ctx, args)
}
// DeleteRoleForUserCtx deletes a role for a user with context support.
// Returns false if the user does not have the role (aka not affected).
func (e *ContextEnforcer) DeleteRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error) {
args := []string{user, role}
args = append(args, domain...)
return e.RemoveGroupingPolicyCtx(ctx, args)
}
// DeleteRolesForUserCtx deletes all roles for a user with context support.
// Returns false if the user does not have any roles (aka not affected).
func (e *ContextEnforcer) DeleteRolesForUserCtx(ctx context.Context, user string, domain ...string) (bool, error) {
var args []string
if len(domain) == 0 {
args = []string{user}
} else if len(domain) > 1 {
return false, errors.ErrDomainParameter
} else {
args = []string{user, "", domain[0]}
}
return e.RemoveFilteredGroupingPolicyCtx(ctx, 0, args...)
}
// DeleteUserCtx deletes a user with context support.
// Returns false if the user does not exist (aka not affected).
func (e *ContextEnforcer) DeleteUserCtx(ctx context.Context, user string) (bool, error) {
var err error
res1, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 0, user)
if err != nil {
return res1, err
}
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
res2, err := e.RemoveFilteredPolicyCtx(ctx, subIndex, user)
return res1 || res2, err
}
// DeleteRoleCtx deletes a role with context support.
// Returns false if the role does not exist (aka not affected).
func (e *ContextEnforcer) DeleteRoleCtx(ctx context.Context, role string) (bool, error) {
var err error
res1, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 0, role)
if err != nil {
return res1, err
}
res2, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 1, role)
if err != nil {
return res1, err
}
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
res3, err := e.RemoveFilteredPolicyCtx(ctx, subIndex, role)
return res1 || res2 || res3, err
}
// DeletePermissionCtx deletes a permission with context support.
// Returns false if the permission does not exist (aka not affected).
func (e *ContextEnforcer) DeletePermissionCtx(ctx context.Context, permission ...string) (bool, error) {
return e.RemoveFilteredPolicyCtx(ctx, 1, permission...)
}
// AddPermissionForUserCtx adds a permission for a user or role with context support.
// Returns false if the user or role already has the permission (aka not affected).
func (e *ContextEnforcer) AddPermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error) {
return e.AddPolicyCtx(ctx, util.JoinSlice(user, permission...))
}
// AddPermissionsForUserCtx adds multiple permissions for a user or role with context support.
// Returns false if the user or role already has one of the permissions (aka not affected).
func (e *ContextEnforcer) AddPermissionsForUserCtx(ctx context.Context, user string, permissions ...[]string) (bool, error) {
var rules [][]string
for _, permission := range permissions {
rules = append(rules, util.JoinSlice(user, permission...))
}
return e.AddPoliciesCtx(ctx, rules)
}
// DeletePermissionForUserCtx deletes a permission for a user or role with context support.
// Returns false if the user or role does not have the permission (aka not affected).
func (e *ContextEnforcer) DeletePermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error) {
return e.RemovePolicyCtx(ctx, util.JoinSlice(user, permission...))
}
// DeletePermissionsForUserCtx deletes permissions for a user or role with context support.
// Returns false if the user or role does not have any permissions (aka not affected).
func (e *ContextEnforcer) DeletePermissionsForUserCtx(ctx context.Context, user string) (bool, error) {
subIndex, err := e.GetFieldIndex("p", constant.SubjectIndex)
if err != nil {
return false, err
}
return e.RemoveFilteredPolicyCtx(ctx, subIndex, user)
}
================================================
FILE: rbac_api_synced.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
// GetRolesForUser gets the roles that a user has.
func (e *SyncedEnforcer) GetRolesForUser(name string, domain ...string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetRolesForUser(name, domain...)
}
// GetUsersForRole gets the users that has a role.
func (e *SyncedEnforcer) GetUsersForRole(name string, domain ...string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetUsersForRole(name, domain...)
}
// HasRoleForUser determines whether a user has a role.
func (e *SyncedEnforcer) HasRoleForUser(name string, role string, domain ...string) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasRoleForUser(name, role, domain...)
}
// AddRoleForUser adds a role for a user.
// Returns false if the user already has the role (aka not affected).
func (e *SyncedEnforcer) AddRoleForUser(user string, role string, domain ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddRoleForUser(user, role, domain...)
}
// AddRolesForUser adds roles for a user.
// Returns false if the user already has the roles (aka not affected).
func (e *SyncedEnforcer) AddRolesForUser(user string, roles []string, domain ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddRolesForUser(user, roles, domain...)
}
// DeleteRoleForUser deletes a role for a user.
// Returns false if the user does not have the role (aka not affected).
func (e *SyncedEnforcer) DeleteRoleForUser(user string, role string, domain ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteRoleForUser(user, role, domain...)
}
// DeleteRolesForUser deletes all roles for a user.
// Returns false if the user does not have any roles (aka not affected).
func (e *SyncedEnforcer) DeleteRolesForUser(user string, domain ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteRolesForUser(user, domain...)
}
// DeleteUser deletes a user.
// Returns false if the user does not exist (aka not affected).
func (e *SyncedEnforcer) DeleteUser(user string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteUser(user)
}
// DeleteRole deletes a role.
// Returns false if the role does not exist (aka not affected).
func (e *SyncedEnforcer) DeleteRole(role string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteRole(role)
}
// DeletePermission deletes a permission.
// Returns false if the permission does not exist (aka not affected).
func (e *SyncedEnforcer) DeletePermission(permission ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeletePermission(permission...)
}
// AddPermissionForUser adds a permission for a user or role.
// Returns false if the user or role already has the permission (aka not affected).
func (e *SyncedEnforcer) AddPermissionForUser(user string, permission ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddPermissionForUser(user, permission...)
}
// AddPermissionsForUser adds permissions for a user or role.
// Returns false if the user or role already has the permissions (aka not affected).
func (e *SyncedEnforcer) AddPermissionsForUser(user string, permissions ...[]string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddPermissionsForUser(user, permissions...)
}
// DeletePermissionForUser deletes a permission for a user or role.
// Returns false if the user or role does not have the permission (aka not affected).
func (e *SyncedEnforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeletePermissionForUser(user, permission...)
}
// DeletePermissionsForUser deletes permissions for a user or role.
// Returns false if the user or role does not have any permissions (aka not affected).
func (e *SyncedEnforcer) DeletePermissionsForUser(user string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeletePermissionsForUser(user)
}
// GetPermissionsForUser gets permissions for a user or role.
func (e *SyncedEnforcer) GetPermissionsForUser(user string, domain ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetPermissionsForUser(user, domain...)
}
// GetNamedPermissionsForUser gets permissions for a user or role by named policy.
func (e *SyncedEnforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetNamedPermissionsForUser(ptype, user, domain...)
}
// HasPermissionForUser determines whether a user has a permission.
func (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.HasPermissionForUser(user, permission...)
}
// GetImplicitRolesForUser gets implicit roles that a user has.
// Compared to GetRolesForUser(), this function retrieves indirect roles besides direct roles.
// For example:
// g, alice, role:admin
// g, role:admin, role:user
//
// GetRolesForUser("alice") can only get: ["role:admin"].
// But GetImplicitRolesForUser("alice") will get: ["role:admin", "role:user"].
func (e *SyncedEnforcer) GetImplicitRolesForUser(name string, domain ...string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetImplicitRolesForUser(name, domain...)
}
// GetImplicitPermissionsForUser gets implicit permissions for a user or role.
// Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles.
// For example:
// p, admin, data1, read
// p, alice, data2, read
// g, alice, admin
//
// GetPermissionsForUser("alice") can only get: [["alice", "data2", "read"]].
// But GetImplicitPermissionsForUser("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]].
func (e *SyncedEnforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.GetImplicitPermissionsForUser(user, domain...)
}
// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.
// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.
// For example:
// p, admin, data1, read
// p2, admin, create
// g, alice, admin
//
// GetImplicitPermissionsForUser("alice") can only get: [["admin", "data1", "read"]], whose policy is default policy "p"
// But you can specify the named policy "p2" to get: [["admin", "create"]] by GetNamedImplicitPermissionsForUser("p2","alice").
func (e *SyncedEnforcer) GetNamedImplicitPermissionsForUser(ptype string, gtype string, user string, domain ...string) ([][]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetNamedImplicitPermissionsForUser(ptype, gtype, user, domain...)
}
// GetImplicitUsersForPermission gets implicit users for a permission.
// For example:
// p, admin, data1, read
// p, bob, data1, read
// g, alice, admin
//
// GetImplicitUsersForPermission("data1", "read") will get: ["alice", "bob"].
// Note: only users will be returned, roles (2nd arg in "g") will be excluded.
func (e *SyncedEnforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetImplicitUsersForPermission(permission...)
}
// GetImplicitObjectPatternsForUser returns all object patterns (with wildcards) that a user has for a given domain and action.
// For example:
// p, admin, chronicle/123, location/*, read
// p, user, chronicle/456, location/789, read
// g, alice, admin
// g, bob, user
//
// GetImplicitObjectPatternsForUser("alice", "chronicle/123", "read") will return ["location/*"].
// GetImplicitObjectPatternsForUser("bob", "chronicle/456", "read") will return ["location/789"].
func (e *SyncedEnforcer) GetImplicitObjectPatternsForUser(user string, domain string, action string) ([]string, error) {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetImplicitObjectPatternsForUser(user, domain, action)
}
================================================
FILE: rbac_api_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"log"
"sort"
"testing"
"github.com/casbin/casbin/v3/constant"
"github.com/casbin/casbin/v3/errors"
defaultrolemanager "github.com/casbin/casbin/v3/rbac/default-role-manager"
"github.com/casbin/casbin/v3/util"
)
func testGetRoles(t *testing.T, e *Enforcer, res []string, name string, domain ...string) {
t.Helper()
myRes, err := e.GetRolesForUser(name, domain...)
if err != nil {
t.Error("Roles for ", name, " could not be fetched: ", err.Error())
}
t.Log("Roles for ", name, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Roles for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func testGetUsers(t *testing.T, e *Enforcer, res []string, name string, domain ...string) {
t.Helper()
myRes, err := e.GetUsersForRole(name, domain...)
switch err {
case nil:
break
case errors.ErrNameNotFound:
t.Log("No name found")
default:
t.Error("Users for ", name, " could not be fetched: ", err.Error())
}
t.Log("Users for ", name, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Users for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func testHasRole(t *testing.T, e *Enforcer, name string, role string, res bool, domain ...string) {
t.Helper()
myRes, err := e.HasRoleForUser(name, role, domain...)
if err != nil {
t.Error("HasRoleForUser returned an error: ", err.Error())
}
t.Log(name, " has role ", role, ": ", myRes)
if res != myRes {
t.Error(name, " has role ", role, ": ", myRes, ", supposed to be ", res)
}
}
func TestRoleAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testGetRoles(t, e, []string{"data2_admin"}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "data2_admin")
testGetRoles(t, e, []string{}, "non_exist")
testHasRole(t, e, "alice", "data1_admin", false)
testHasRole(t, e, "alice", "data2_admin", true)
_, _ = e.AddRoleForUser("alice", "data1_admin")
testGetRoles(t, e, []string{"data1_admin", "data2_admin"}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "data2_admin")
_, _ = e.DeleteRoleForUser("alice", "data1_admin")
testGetRoles(t, e, []string{"data2_admin"}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "data2_admin")
_, _ = e.DeleteRolesForUser("alice")
testGetRoles(t, e, []string{}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "data2_admin")
_, _ = e.AddRoleForUser("alice", "data1_admin")
_, _ = e.DeleteUser("alice")
testGetRoles(t, e, []string{}, "alice")
testGetRoles(t, e, []string{}, "bob")
testGetRoles(t, e, []string{}, "data2_admin")
_, _ = e.AddRoleForUser("alice", "data2_admin")
testEnforce(t, e, "alice", "data1", "read", false)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
_, _ = e.DeleteRole("data2_admin")
testEnforce(t, e, "alice", "data1", "read", false)
testEnforce(t, e, "alice", "data1", "write", false)
testEnforce(t, e, "alice", "data2", "read", false)
testEnforce(t, e, "alice", "data2", "write", false)
testEnforce(t, e, "bob", "data1", "read", false)
testEnforce(t, e, "bob", "data1", "write", false)
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
}
func TestRoleAPI_Domains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testHasRole(t, e, "alice", "admin", true, "domain1")
testHasRole(t, e, "alice", "admin", false, "domain2")
testGetRoles(t, e, []string{"admin"}, "alice", "domain1")
testGetRoles(t, e, []string{}, "bob", "domain1")
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRoles(t, e, []string{}, "non_exist", "domain2")
_, _ = e.DeleteRoleForUser("alice", "admin", "domain1")
_, _ = e.AddRoleForUser("bob", "admin", "domain1")
testGetRoles(t, e, []string{}, "alice", "domain1")
testGetRoles(t, e, []string{"admin"}, "bob", "domain1")
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRoles(t, e, []string{}, "non_exist", "domain2")
_, _ = e.AddRoleForUser("alice", "admin", "domain1")
_, _ = e.DeleteRolesForUser("bob", "domain1")
testGetRoles(t, e, []string{"admin"}, "alice", "domain1")
testGetRoles(t, e, []string{}, "bob", "domain1")
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRoles(t, e, []string{}, "non_exist", "domain2")
_, _ = e.AddRolesForUser("bob", []string{"admin", "admin1", "admin2"}, "domain1")
testGetRoles(t, e, []string{"admin", "admin1", "admin2"}, "bob", "domain1")
testGetPermissions(t, e, "admin", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}}, "domain1")
testGetPermissions(t, e, "admin", [][]string{{"admin", "domain2", "data2", "read"}, {"admin", "domain2", "data2", "write"}}, "domain2")
}
func TestEnforcer_AddRolesForUser(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
_, _ = e.AddRolesForUser("alice", []string{"data1_admin", "data2_admin", "data3_admin"})
// The "alice" already has "data2_admin" , it will be return false. So "alice" just has "data2_admin".
testGetRoles(t, e, []string{"data2_admin"}, "alice")
// delete role
_, _ = e.DeleteRoleForUser("alice", "data2_admin")
_, _ = e.AddRolesForUser("alice", []string{"data1_admin", "data2_admin", "data3_admin"})
testGetRoles(t, e, []string{"data1_admin", "data2_admin", "data3_admin"}, "alice")
testEnforce(t, e, "alice", "data1", "read", true)
testEnforce(t, e, "alice", "data2", "read", true)
testEnforce(t, e, "alice", "data2", "write", true)
}
func testGetPermissions(t *testing.T, e *Enforcer, name string, res [][]string, domain ...string) {
t.Helper()
myRes, err := e.GetPermissionsForUser(name, domain...)
if err != nil {
t.Error(err.Error())
}
t.Log("Permissions for ", name, ": ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Permissions for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func testHasPermission(t *testing.T, e *Enforcer, name string, permission []string, res bool) {
t.Helper()
myRes, err := e.HasPermissionForUser(name, permission...)
if err != nil {
t.Error(err.Error())
}
t.Log(name, " has permission ", util.ArrayToString(permission), ": ", myRes)
if res != myRes {
t.Error(name, " has permission ", util.ArrayToString(permission), ": ", myRes, ", supposed to be ", res)
}
}
func testGetNamedPermissionsForUser(t *testing.T, e *Enforcer, ptype string, name string, res [][]string, domain ...string) {
t.Helper()
myRes, err := e.GetNamedPermissionsForUser(ptype, name, domain...)
if err != nil {
t.Error(err.Error())
}
t.Log("Named permissions for ", name, ": ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Named permissions for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func TestPermissionAPI(t *testing.T) {
e, _ := NewEnforcer("examples/basic_without_resources_model.conf", "examples/basic_without_resources_policy.csv")
testEnforceWithoutUsers(t, e, "alice", "read", true)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", false)
testEnforceWithoutUsers(t, e, "bob", "write", true)
testGetPermissions(t, e, "alice", [][]string{{"alice", "read"}})
testGetPermissions(t, e, "bob", [][]string{{"bob", "write"}})
testHasPermission(t, e, "alice", []string{"read"}, true)
testHasPermission(t, e, "alice", []string{"write"}, false)
testHasPermission(t, e, "bob", []string{"read"}, false)
testHasPermission(t, e, "bob", []string{"write"}, true)
_, _ = e.DeletePermission("read")
testEnforceWithoutUsers(t, e, "alice", "read", false)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", false)
testEnforceWithoutUsers(t, e, "bob", "write", true)
_, _ = e.AddPermissionForUser("bob", "read")
testEnforceWithoutUsers(t, e, "alice", "read", false)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", true)
testEnforceWithoutUsers(t, e, "bob", "write", true)
_, _ = e.AddPermissionsForUser("jack",
[]string{"read"},
[]string{"write"})
testEnforceWithoutUsers(t, e, "jack", "read", true)
testEnforceWithoutUsers(t, e, "bob", "write", true)
_, _ = e.DeletePermissionForUser("bob", "read")
testEnforceWithoutUsers(t, e, "alice", "read", false)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", false)
testEnforceWithoutUsers(t, e, "bob", "write", true)
_, _ = e.DeletePermissionsForUser("bob")
testEnforceWithoutUsers(t, e, "alice", "read", false)
testEnforceWithoutUsers(t, e, "alice", "write", false)
testEnforceWithoutUsers(t, e, "bob", "read", false)
testEnforceWithoutUsers(t, e, "bob", "write", false)
e, _ = NewEnforcer("examples/rbac_with_multiple_policy_model.conf", "examples/rbac_with_multiple_policy_policy.csv")
testGetNamedPermissionsForUser(t, e, "p", "user", [][]string{{"user", "/data", "GET"}})
testGetNamedPermissionsForUser(t, e, "p2", "user", [][]string{{"user", "view"}})
}
func testGetImplicitRoles(t *testing.T, e *Enforcer, name string, res []string) {
t.Helper()
myRes, _ := e.GetImplicitRolesForUser(name)
t.Log("Implicit roles for ", name, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Implicit roles for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func testGetImplicitRolesInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {
t.Helper()
myRes, _ := e.GetImplicitRolesForUser(name, domain)
t.Log("Implicit roles in domain ", domain, " for ", name, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Implicit roles in domain ", domain, " for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func TestImplicitRoleAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv")
testGetPermissions(t, e, "alice", [][]string{{"alice", "data1", "read"}})
testGetPermissions(t, e, "bob", [][]string{{"bob", "data2", "write"}})
testGetImplicitRoles(t, e, "alice", []string{"admin", "data1_admin", "data2_admin"})
testGetImplicitRoles(t, e, "bob", []string{})
e, _ = NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
e.GetRoleManager().AddMatchingFunc("matcher", util.KeyMatch)
e.AddNamedMatchingFunc("g2", "matcher", util.KeyMatch)
// testGetImplicitRoles(t, e, "cathy", []string{"/book/1/2/3/4/5", "pen_admin", "/book/*", "book_group"})
testGetImplicitRoles(t, e, "cathy", []string{"/book/1/2/3/4/5", "pen_admin"})
testGetRoles(t, e, []string{"/book/1/2/3/4/5", "pen_admin"}, "cathy")
}
func testGetImplicitPermissions(t *testing.T, e *Enforcer, name string, res [][]string, domain ...string) {
t.Helper()
myRes, _ := e.GetImplicitPermissionsForUser(name, domain...)
t.Log("Implicit permissions for ", name, ": ", myRes)
if !util.Set2DEquals(res, myRes) {
t.Error("Implicit permissions for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func testGetImplicitPermissionsWithDomain(t *testing.T, e *Enforcer, name string, domain string, res [][]string) {
t.Helper()
myRes, _ := e.GetImplicitPermissionsForUser(name, domain)
t.Log("Implicit permissions for", name, "under", domain, ":", myRes)
if !util.Set2DEquals(res, myRes) {
t.Error("Implicit permissions for", name, "under", domain, ":", myRes, ", supposed to be ", res)
}
}
func testGetNamedImplicitPermissions(t *testing.T, e *Enforcer, ptype string, gtype string, name string, res [][]string) {
t.Helper()
myRes, _ := e.GetNamedImplicitPermissionsForUser(ptype, gtype, name)
t.Log("Named implicit permissions for ", name, ": ", myRes)
if !util.Set2DEquals(res, myRes) {
t.Error("Named implicit permissions for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func TestImplicitPermissionAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv")
testGetPermissions(t, e, "alice", [][]string{{"alice", "data1", "read"}})
testGetPermissions(t, e, "bob", [][]string{{"bob", "data2", "write"}})
testGetImplicitPermissions(t, e, "alice", [][]string{{"alice", "data1", "read"}, {"data1_admin", "data1", "read"}, {"data1_admin", "data1", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
testGetImplicitPermissions(t, e, "bob", [][]string{{"bob", "data2", "write"}})
e, _ = NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
e.AddNamedDomainMatchingFunc("g", "KeyMatch", util.KeyMatch)
testGetImplicitPermissions(t, e, "admin", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}, {"admin", "domain1", "data3", "read"}}, "domain1")
_, err := e.GetImplicitPermissionsForUser("admin", "domain1", "domain2")
if err == nil {
t.Error("GetImplicitPermissionsForUser should not support multiple domains")
}
testGetImplicitPermissions(t, e, "alice",
[][]string{{"admin", "domain2", "data2", "read"}, {"admin", "domain2", "data2", "write"}, {"admin", "domain2", "data3", "read"}},
"domain2")
e, _ = NewEnforcer("examples/rbac_with_multiple_policy_model.conf", "examples/rbac_with_multiple_policy_policy.csv")
testGetNamedImplicitPermissions(t, e, "p", "g", "alice", [][]string{{"user", "/data", "GET"}, {"admin", "/data", "POST"}})
testGetNamedImplicitPermissions(t, e, "p2", "g", "alice", [][]string{{"user", "view"}, {"admin", "create"}})
testGetNamedImplicitPermissions(t, e, "p", "g2", "alice", [][]string{{"user", "/data", "GET"}})
testGetNamedImplicitPermissions(t, e, "p2", "g2", "alice", [][]string{{"user", "view"}})
}
func TestImplicitPermissionAPIWithDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_hierarchy_with_domains_policy.csv")
testGetImplicitPermissionsWithDomain(t, e, "alice", "domain1", [][]string{{"alice", "domain1", "data2", "read"}, {"role:reader", "domain1", "data1", "read"}, {"role:writer", "domain1", "data1", "write"}})
}
func testGetImplicitUsers(t *testing.T, e *Enforcer, res []string, permission ...string) {
t.Helper()
myRes, _ := e.GetImplicitUsersForPermission(permission...)
t.Log("Implicit users for permission: ", permission, ": ", myRes)
sort.Strings(res)
sort.Strings(myRes)
if !util.ArrayEquals(res, myRes) {
t.Error("Implicit users for permission: ", permission, ": ", myRes, ", supposed to be ", res)
}
}
func TestImplicitUserAPI(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv")
testGetImplicitUsers(t, e, []string{"alice"}, "data1", "read")
testGetImplicitUsers(t, e, []string{"alice"}, "data1", "write")
testGetImplicitUsers(t, e, []string{"alice"}, "data2", "read")
testGetImplicitUsers(t, e, []string{"alice", "bob"}, "data2", "write")
e.ClearPolicy()
_, _ = e.AddPolicy("admin", "data1", "read")
_, _ = e.AddPolicy("bob", "data1", "read")
_, _ = e.AddGroupingPolicy("alice", "admin")
testGetImplicitUsers(t, e, []string{"alice", "bob"}, "data1", "read")
}
func testGetImplicitResourcesForUser(t *testing.T, e *Enforcer, res [][]string, user string, domain ...string) {
t.Helper()
myRes, _ := e.GetImplicitResourcesForUser(user, domain...)
t.Log("Implicit resources for user: ", user, ": ", myRes)
lessFunc := func(arr [][]string) func(int, int) bool {
return func(i, j int) bool {
policy1, policy2 := arr[i], arr[j]
for k := range policy1 {
if policy1[k] == policy2[k] {
continue
}
return policy1[k] < policy2[k]
}
return true
}
}
sort.Slice(res, lessFunc(res))
sort.Slice(myRes, lessFunc(myRes))
if !util.Array2DEquals(res, myRes) {
t.Error("Implicit resources for user: ", user, ": ", myRes, ", supposed to be ", res)
}
}
func TestGetImplicitResourcesForUser(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
testGetImplicitResourcesForUser(t, e, [][]string{
{"alice", "/pen/1", "GET"},
{"alice", "/pen2/1", "GET"},
{"alice", "/book/:id", "GET"},
{"alice", "/book2/{id}", "GET"},
{"alice", "/book/*", "GET"},
{"alice", "book_group", "GET"},
}, "alice")
testGetImplicitResourcesForUser(t, e, [][]string{
{"bob", "pen_group", "GET"},
{"bob", "/pen/:id", "GET"},
{"bob", "/pen2/{id}", "GET"},
}, "bob")
testGetImplicitResourcesForUser(t, e, [][]string{
{"cathy", "pen_group", "GET"},
{"cathy", "/pen/:id", "GET"},
{"cathy", "/pen2/{id}", "GET"},
}, "cathy")
}
func TestImplicitUsersForRole(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
testGetImplicitUsersForRole(t, e, "book_admin", []string{"alice"})
testGetImplicitUsersForRole(t, e, "pen_admin", []string{"cathy", "bob"})
testGetImplicitUsersForRole(t, e, "book_group", []string{"/book/*", "/book/:id", "/book2/{id}"})
testGetImplicitUsersForRole(t, e, "pen_group", []string{"/pen/:id", "/pen2/{id}"})
}
func testGetImplicitUsersForRole(t *testing.T, e *Enforcer, name string, res []string) {
t.Helper()
myRes, _ := e.GetImplicitUsersForRole(name)
t.Log("Implicit users for ", name, ": ", myRes)
sort.Strings(res)
sort.Strings(myRes)
if !util.SetEquals(res, myRes) {
t.Error("Implicit users for ", name, ": ", myRes, ", supposed to be ", res)
}
}
func TestExplicitPriorityModify(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
testEnforce(t, e, "bob", "data2", "write", true)
_, err := e.AddPolicy("1", "bob", "data2", "write", "deny")
if err != nil {
t.Fatalf("AddPolicy: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", false)
_, err = e.DeletePermissionsForUser("bob")
if err != nil {
t.Fatalf("DeletePermissionForUser: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", true)
_, err = e.DeleteRole("data2_allow_group")
if err != nil {
t.Fatalf("DeleteRole: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", false)
}
func TestCustomizedFieldIndex(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model_explicit_customized.conf",
"examples/priority_policy_explicit_customized.csv")
// Due to the customized priority token, the enforcer failed to handle the priority.
testEnforce(t, e, "bob", "data2", "read", true)
// set PriorityIndex and reload
e.SetFieldIndex("p", constant.PriorityIndex, 0)
err := e.LoadPolicy()
if err != nil {
t.Fatalf("LoadPolicy: %v", err)
}
testEnforce(t, e, "bob", "data2", "read", false)
testEnforce(t, e, "bob", "data2", "write", true)
_, err = e.AddPolicy("1", "data2", "write", "deny", "bob")
if err != nil {
t.Fatalf("AddPolicy: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", false)
// Due to the customized subject token, the enforcer will raise an error before SetFieldIndex.
_, err = e.DeletePermissionsForUser("bob")
if err == nil {
t.Fatalf("Failed to warning SetFieldIndex")
}
e.SetFieldIndex("p", constant.SubjectIndex, 4)
_, err = e.DeletePermissionsForUser("bob")
if err != nil {
t.Fatalf("DeletePermissionForUser: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", true)
_, err = e.DeleteRole("data2_allow_group")
if err != nil {
t.Fatalf("DeleteRole: %v", err)
}
testEnforce(t, e, "bob", "data2", "write", false)
}
func testGetAllowedObjectConditions(t *testing.T, e *Enforcer, user string, act string, prefix string, res []string, expectedErr error) {
myRes, actualErr := e.GetAllowedObjectConditions(user, act, prefix)
if actualErr != expectedErr {
t.Error("actual Err: ", actualErr, ", supposed to be ", expectedErr)
}
if actualErr == nil {
log.Print("Policy: ", myRes)
if !util.ArrayEquals(res, myRes) {
t.Error("Policy: ", myRes, ", supposed to be ", res)
}
}
}
func TestGetAllowedObjectConditions(t *testing.T) {
e, _ := NewEnforcer("examples/object_conditions_model.conf", "examples/object_conditions_policy.csv")
testGetAllowedObjectConditions(t, e, "alice", "read", "r.obj.", []string{"price < 25", "category_id = 2"}, nil)
testGetAllowedObjectConditions(t, e, "admin", "read", "r.obj.", []string{"category_id = 2"}, nil)
testGetAllowedObjectConditions(t, e, "bob", "write", "r.obj.", []string{"author = bob"}, nil)
// test ErrEmptyCondition
testGetAllowedObjectConditions(t, e, "alice", "write", "r.obj.", []string{}, errors.ErrEmptyCondition)
testGetAllowedObjectConditions(t, e, "bob", "read", "r.obj.", []string{}, errors.ErrEmptyCondition)
// test ErrObjCondition
// should : e.AddPolicy("alice", "r.obj.price > 50", "read")
ok, _ := e.AddPolicy("alice", "price > 50", "read")
if ok {
testGetAllowedObjectConditions(t, e, "alice", "read", "r.obj.", []string{}, errors.ErrObjCondition)
}
// test prefix
e.ClearPolicy()
err := e.GetRoleManager().DeleteLink("alice", "admin")
if err != nil {
panic(err)
}
ok, _ = e.AddPolicies([][]string{
{"alice", "r.book.price < 25", "read"},
{"admin", "r.book.category_id = 2", "read"},
{"bob", "r.book.author = bob", "write"},
})
if ok {
testGetAllowedObjectConditions(t, e, "alice", "read", "r.book.", []string{"price < 25"}, nil)
testGetAllowedObjectConditions(t, e, "admin", "read", "r.book.", []string{"category_id = 2"}, nil)
testGetAllowedObjectConditions(t, e, "bob", "write", "r.book.", []string{"author = bob"}, nil)
}
}
func testGetImplicitUsersForResource(t *testing.T, e *Enforcer, res [][]string, resource string, domain ...string) {
t.Helper()
myRes, err := e.GetImplicitUsersForResource(resource)
if err != nil {
panic(err)
}
if !util.Set2DEquals(res, myRes) {
t.Error("Implicit users for ", resource, "in domain ", domain, " : ", myRes, ", supposed to be ", res)
} else {
t.Log("Implicit users for ", resource, "in domain ", domain, " : ", myRes)
}
}
func TestGetImplicitUsersForResource(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
testGetImplicitUsersForResource(t, e, [][]string{{"alice", "data1", "read"}}, "data1")
testGetImplicitUsersForResource(t, e, [][]string{{"bob", "data2", "write"},
{"alice", "data2", "read"},
{"alice", "data2", "write"}}, "data2")
// test duplicate permissions
_, _ = e.AddGroupingPolicy("alice", "data2_admin_2")
_, _ = e.AddPolicies([][]string{{"data2_admin_2", "data2", "read"}, {"data2_admin_2", "data2", "write"}})
testGetImplicitUsersForResource(t, e, [][]string{{"bob", "data2", "write"},
{"alice", "data2", "read"},
{"alice", "data2", "write"}}, "data2")
}
func TestGetImplicitUsersForResourceWithResourceRoles(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_resource_roles_model.conf", "examples/rbac_with_resource_roles_policy.csv")
// Test data1 resource - should return users who have access through g2 relationships
data1Users, err := e.GetNamedImplicitUsersForResource("g2", "data1")
if err != nil {
t.Fatalf("GetNamedImplicitUsersForResource failed: %v", err)
}
expectedData1Users := 2 // [alice data1 read] + [alice data_group write]
if len(data1Users) != expectedData1Users {
t.Errorf("Expected %d users for data1 resource, got %d: %v", expectedData1Users, len(data1Users), data1Users)
}
// Test data2 resource - should return users who have access through g2 relationships
data2Users, err := e.GetNamedImplicitUsersForResource("g2", "data2")
if err != nil {
t.Fatalf("GetNamedImplicitUsersForResource failed: %v", err)
}
expectedData2Users := 2 // [bob data2 write] + [alice data_group write]
if len(data2Users) != expectedData2Users {
t.Errorf("Expected %d users for data2 resource, got %d: %v", expectedData2Users, len(data2Users), data2Users)
}
// Test with "g" policy type - should return users who have access through g relationships
data1UsersG, err := e.GetNamedImplicitUsersForResource("g", "data1")
if err != nil {
t.Fatalf("GetNamedImplicitUsersForResource with g failed: %v", err)
}
expectedData1UsersG := 1 // [alice data1 read] only
if len(data1UsersG) != expectedData1UsersG {
t.Errorf("Expected %d users for data1 resource with g policy, got %d: %v", expectedData1UsersG, len(data1UsersG), data1UsersG)
}
}
func testGetImplicitUsersForResourceByDomain(t *testing.T, e *Enforcer, res [][]string, resource string, domain string) {
t.Helper()
myRes, err := e.GetImplicitUsersForResourceByDomain(resource, domain)
if err != nil {
panic(err)
}
if !util.Set2DEquals(res, myRes) {
t.Error("Implicit users for ", resource, "in domain ", domain, " : ", myRes, ", supposed to be ", res)
} else {
t.Log("Implicit users for ", resource, "in domain ", domain, " : ", myRes)
}
}
func TestGetImplicitUsersForResourceByDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetImplicitUsersForResourceByDomain(t, e, [][]string{{"alice", "domain1", "data1", "read"},
{"alice", "domain1", "data1", "write"}}, "data1", "domain1")
testGetImplicitUsersForResourceByDomain(t, e, [][]string{}, "data2", "domain1")
testGetImplicitUsersForResourceByDomain(t, e, [][]string{{"bob", "domain2", "data2", "read"},
{"bob", "domain2", "data2", "write"}}, "data2", "domain2")
}
func TestConditional(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_conditional_model.conf", "examples/rbac_with_domains_conditional_policy.csv")
g, _ := e.GetNamedGroupingPolicy("g")
for _, gp := range g {
e.AddNamedDomainLinkConditionFunc("g", gp[0], gp[1], gp[2], util.TimeMatchFunc)
}
testDomainEnforce(t, e, "alice", "domain1", "service1", "/list", true)
testDomainEnforce(t, e, "bob", "domain2", "service2", "/broadcast", true)
testDomainEnforce(t, e, "jack", "domain1", "service1", "/list", false)
testGetImplicitRolesInDomain(t, e, "alice", "domain1", []string{"test1"})
testGetRolesInDomain(t, e, "alice", "domain1", []string{"test1"})
testGetUsersInDomain(t, e, "test1", "domain1", []string{"alice"})
}
func TestMaxHierarchyLevelConsistency(t *testing.T) {
// Test consistency behavior under different maxHierarchyLevel values
testCases := []struct {
maxLevel int
name string
}{
{1, "maxHierarchyLevel=1"},
{2, "maxHierarchyLevel=2"},
{3, "maxHierarchyLevel=3"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Use model files from examples
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Set the maximum hierarchy level for role manager
rm := defaultrolemanager.NewRoleManager(tc.maxLevel)
e.SetRoleManager(rm)
// Add role hierarchy: level0 -> level1 -> level2 -> level3 -> level4
_, err = e.AddRoleForUser("level0", "level1")
if err != nil {
t.Fatalf("Failed to add role for user: %v", err)
}
_, err = e.AddRoleForUser("level1", "level2")
if err != nil {
t.Fatalf("Failed to add role for user: %v", err)
}
_, err = e.AddRoleForUser("level2", "level3")
if err != nil {
t.Fatalf("Failed to add role for user: %v", err)
}
_, err = e.AddRoleForUser("level3", "level4")
if err != nil {
t.Fatalf("Failed to add role for user: %v", err)
}
// Test HasLink method
t.Run("HasLink", func(t *testing.T) {
for i := 1; i <= 4; i++ {
hasLink, err := rm.HasLink("level0", fmt.Sprintf("level%d", i))
if err != nil {
t.Fatalf("HasLink error: %v", err)
}
expected := i <= tc.maxLevel
if hasLink != expected {
t.Errorf("HasLink(level0, level%d): got %v, want %v", i, hasLink, expected)
}
}
})
// Test GetImplicitRolesForUser method
t.Run("GetImplicitRolesForUser", func(t *testing.T) {
implicitRoles, err := e.GetImplicitRolesForUser("level0")
if err != nil {
t.Fatalf("GetImplicitRolesForUser error: %v", err)
}
expectedCount := tc.maxLevel
if len(implicitRoles) != expectedCount {
t.Errorf("GetImplicitRolesForUser(level0): got %d roles %v, want %d roles",
len(implicitRoles), implicitRoles, expectedCount)
}
// Verify that returned roles are correct
for i := 1; i <= tc.maxLevel; i++ {
expectedRole := fmt.Sprintf("level%d", i)
found := false
for _, role := range implicitRoles {
if role == expectedRole {
found = true
break
}
}
if !found {
t.Errorf("Expected role %s not found in implicit roles: %v", expectedRole, implicitRoles)
}
}
})
// Test GetImplicitUsersForRole method
t.Run("GetImplicitUsersForRole", func(t *testing.T) {
implicitUsers, err := e.GetImplicitUsersForRole("level4")
if err != nil {
t.Fatalf("GetImplicitUsersForRole error: %v", err)
}
expectedCount := tc.maxLevel
if len(implicitUsers) != expectedCount {
t.Errorf("GetImplicitUsersForRole(level4): got %d users %v, want %d users",
len(implicitUsers), implicitUsers, expectedCount)
}
// Verify that returned users are correct (starting from level3 upward)
for i := 0; i < tc.maxLevel; i++ {
expectedUser := fmt.Sprintf("level%d", 3-i)
found := false
for _, user := range implicitUsers {
if user == expectedUser {
found = true
break
}
}
if !found {
t.Errorf("Expected user %s not found in implicit users: %v", expectedUser, implicitUsers)
}
}
})
// Test implicit roles for different users
t.Run("DifferentUsersImplicitRoles", func(t *testing.T) {
for i := 0; i <= 3; i++ {
user := fmt.Sprintf("level%d", i)
implicitRoles, err := e.GetImplicitRolesForUser(user)
if err != nil {
t.Fatalf("GetImplicitRolesForUser(%s) error: %v", user, err)
}
// Verify that the number of returned roles does not exceed maxHierarchyLevel
if len(implicitRoles) > tc.maxLevel {
t.Errorf("GetImplicitRolesForUser(%s): got %d roles, should not exceed maxHierarchyLevel %d",
user, len(implicitRoles), tc.maxLevel)
}
}
})
})
}
}
func testGetImplicitObjectPatternsForUser(t *testing.T, e *Enforcer, user string, domain string, action string, res []string) {
t.Helper()
myRes, err := e.GetImplicitObjectPatternsForUser(user, domain, action)
if err != nil {
t.Error("Implicit object patterns for ", user, " under domain ", domain, " with action ", action, " could not be fetched: ", err.Error())
}
t.Log("Implicit object patterns for ", user, " under domain ", domain, " with action ", action, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Implicit object patterns for ", user, " under domain ", domain, " with action ", action, ": ", myRes, ", supposed to be ", res)
}
}
func TestGetImplicitObjectPatternsForUser(t *testing.T) {
// Test with domain pattern model
e, _ := NewEnforcer("examples/rbac_with_domain_pattern_model.conf", "examples/rbac_with_domain_pattern_policy.csv")
e.AddNamedDomainMatchingFunc("g", "KeyMatch", util.KeyMatch)
// Test case 1: admin user with wildcard domain access
testGetImplicitObjectPatternsForUser(t, e, "admin", "domain1", "read", []string{"data1", "data3"})
testGetImplicitObjectPatternsForUser(t, e, "admin", "domain1", "write", []string{"data1"})
// Test case 2: alice user inheriting admin role in domain2
testGetImplicitObjectPatternsForUser(t, e, "alice", "domain2", "read", []string{"data2", "data3"})
testGetImplicitObjectPatternsForUser(t, e, "alice", "domain2", "write", []string{"data2"})
// Test case 3: bob user with specific domain access
testGetImplicitObjectPatternsForUser(t, e, "bob", "domain2", "read", []string{"data2", "data3"})
testGetImplicitObjectPatternsForUser(t, e, "bob", "domain2", "write", []string{"data2"})
// Test case 4: non-existent domain (admin has wildcard access to data3)
testGetImplicitObjectPatternsForUser(t, e, "admin", "non_existent", "read", []string{"data3"})
// Test case 5: non-existent action
testGetImplicitObjectPatternsForUser(t, e, "admin", "domain1", "non_existent", []string{})
}
================================================
FILE: rbac_api_with_domains.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"github.com/casbin/casbin/v3/constant"
)
// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon.
func (e *Enforcer) GetUsersForRoleInDomain(name string, domain string) []string {
if e.GetRoleManager() == nil {
return nil
}
res, _ := e.GetRoleManager().GetUsers(name, domain)
return res
}
// GetRolesForUserInDomain gets the roles that a user has inside a domain.
func (e *Enforcer) GetRolesForUserInDomain(name string, domain string) []string {
if rm := e.GetRoleManager(); rm != nil {
res, _ := rm.GetRoles(name, domain)
return res
}
return nil
}
// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain.
func (e *Enforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string {
res, _ := e.GetImplicitPermissionsForUser(user, domain)
return res
}
// AddRoleForUserInDomain adds a role for a user inside a domain.
// Returns false if the user already has the role (aka not affected).
func (e *Enforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) {
return e.AddGroupingPolicy(user, role, domain)
}
// DeleteRoleForUserInDomain deletes a role for a user inside a domain.
// Returns false if the user does not have the role (aka not affected).
func (e *Enforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) {
return e.RemoveGroupingPolicy(user, role, domain)
}
// DeleteRolesForUserInDomain deletes all roles for a user inside a domain.
// Returns false if the user does not have any roles (aka not affected).
func (e *Enforcer) DeleteRolesForUserInDomain(user string, domain string) (bool, error) {
if e.GetRoleManager() == nil {
return false, fmt.Errorf("role manager is not initialized")
}
roles, err := e.GetRoleManager().GetRoles(user, domain)
if err != nil {
return false, err
}
var rules [][]string
for _, role := range roles {
rules = append(rules, []string{user, role, domain})
}
return e.RemoveGroupingPolicies(rules)
}
// GetAllUsersByDomain would get all users associated with the domain.
func (e *Enforcer) GetAllUsersByDomain(domain string) ([]string, error) {
m := make(map[string]struct{})
g, err := e.model.GetAssertion("g", "g")
if err != nil {
return []string{}, err
}
p := e.model["p"]["p"]
users := make([]string, 0)
index, err := e.GetFieldIndex("p", constant.DomainIndex)
if err != nil {
return []string{}, err
}
getUser := func(index int, policies [][]string, domain string, m map[string]struct{}) []string {
if len(policies) == 0 || len(policies[0]) <= index {
return []string{}
}
res := make([]string, 0)
for _, policy := range policies {
if _, ok := m[policy[0]]; policy[index] == domain && !ok {
res = append(res, policy[0])
m[policy[0]] = struct{}{}
}
}
return res
}
users = append(users, getUser(2, g.Policy, domain, m)...)
users = append(users, getUser(index, p.Policy, domain, m)...)
return users, nil
}
// DeleteAllUsersByDomain would delete all users associated with the domain.
func (e *Enforcer) DeleteAllUsersByDomain(domain string) (bool, error) {
g, err := e.model.GetAssertion("g", "g")
if err != nil {
return false, err
}
p := e.model["p"]["p"]
index, err := e.GetFieldIndex("p", constant.DomainIndex)
if err != nil {
return false, err
}
getUser := func(index int, policies [][]string, domain string) [][]string {
if len(policies) == 0 || len(policies[0]) <= index {
return [][]string{}
}
res := make([][]string, 0)
for _, policy := range policies {
if policy[index] == domain {
res = append(res, policy)
}
}
return res
}
users := getUser(2, g.Policy, domain)
if _, err = e.RemoveGroupingPolicies(users); err != nil {
return false, err
}
users = getUser(index, p.Policy, domain)
if _, err = e.RemovePolicies(users); err != nil {
return false, err
}
return true, nil
}
// DeleteDomains would delete all associated policies.
// It would delete all domains if parameter is not provided.
func (e *Enforcer) DeleteDomains(domains ...string) (bool, error) {
if len(domains) == 0 {
var err error
domains, err = e.GetAllDomains()
if err != nil {
return false, err
}
}
for _, domain := range domains {
if _, err := e.DeleteAllUsersByDomain(domain); err != nil {
return false, err
}
// remove the domain from the RoleManager.
if e.GetRoleManager() != nil {
if err := e.GetRoleManager().DeleteDomain(domain); err != nil {
return false, err
}
}
}
return true, nil
}
// GetAllDomains would get all domains.
func (e *Enforcer) GetAllDomains() ([]string, error) {
if e.GetRoleManager() == nil {
return nil, fmt.Errorf("role manager is not initialized")
}
return e.GetRoleManager().GetAllDomains()
}
// GetAllRolesByDomain would get all roles associated with the domain.
// note: Not applicable to Domains with inheritance relationship (implicit roles)
func (e *Enforcer) GetAllRolesByDomain(domain string) ([]string, error) {
g, err := e.model.GetAssertion("g", "g")
if err != nil {
return []string{}, err
}
policies := g.Policy
roles := make([]string, 0)
existMap := make(map[string]bool) // remove duplicates
for _, policy := range policies {
if policy[len(policy)-1] == domain {
role := policy[len(policy)-2]
if _, ok := existMap[role]; !ok {
roles = append(roles, role)
existMap[role] = true
}
}
}
return roles, nil
}
================================================
FILE: rbac_api_with_domains_context.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"fmt"
"github.com/casbin/casbin/v3/constant"
)
// AddRoleForUserInDomainCtx adds a role for a user inside a domain with context support.
// Returns false if the user already has the role (aka not affected).
func (e *ContextEnforcer) AddRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error) {
return e.AddGroupingPolicyCtx(ctx, user, role, domain)
}
// DeleteRoleForUserInDomainCtx deletes a role for a user inside a domain with context support.
// Returns false if the user does not have the role (aka not affected).
func (e *ContextEnforcer) DeleteRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error) {
return e.RemoveGroupingPolicyCtx(ctx, user, role, domain)
}
// DeleteRolesForUserInDomainCtx deletes all roles for a user inside a domain with context support.
// Returns false if the user does not have any roles (aka not affected).
func (e *ContextEnforcer) DeleteRolesForUserInDomainCtx(ctx context.Context, user string, domain string) (bool, error) {
if e.GetRoleManager() == nil {
return false, fmt.Errorf("role manager is not initialized")
}
roles, err := e.GetRoleManager().GetRoles(user, domain)
if err != nil {
return false, err
}
var rules [][]string
for _, role := range roles {
rules = append(rules, []string{user, role, domain})
}
return e.RemoveGroupingPoliciesCtx(ctx, rules)
}
// DeleteAllUsersByDomainCtx deletes all users associated with the domain with context support.
func (e *ContextEnforcer) DeleteAllUsersByDomainCtx(ctx context.Context, domain string) (bool, error) {
g, err := e.model.GetAssertion("g", "g")
if err != nil {
return false, err
}
p := e.model["p"]["p"]
index, err := e.GetFieldIndex("p", constant.DomainIndex)
if err != nil {
return false, err
}
getUser := func(index int, policies [][]string, domain string) [][]string {
if len(policies) == 0 || len(policies[0]) <= index {
return [][]string{}
}
res := make([][]string, 0)
for _, policy := range policies {
if policy[index] == domain {
res = append(res, policy)
}
}
return res
}
users := getUser(2, g.Policy, domain)
if _, err = e.RemoveGroupingPoliciesCtx(ctx, users); err != nil {
return false, err
}
users = getUser(index, p.Policy, domain)
if _, err = e.RemovePoliciesCtx(ctx, users); err != nil {
return false, err
}
return true, nil
}
// DeleteDomainsCtx deletes all associated policies for domains with context support.
// It would delete all domains if parameter is not provided.
func (e *ContextEnforcer) DeleteDomainsCtx(ctx context.Context, domains ...string) (bool, error) {
if len(domains) == 0 {
var err error
domains, err = e.GetAllDomains()
if err != nil {
return false, err
}
}
for _, domain := range domains {
if _, err := e.DeleteAllUsersByDomainCtx(ctx, domain); err != nil {
return false, err
}
// remove the domain from the RoleManager.
if e.GetRoleManager() != nil {
if err := e.GetRoleManager().DeleteDomain(domain); err != nil {
return false, err
}
}
}
return true, nil
}
================================================
FILE: rbac_api_with_domains_synced.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon.
func (e *SyncedEnforcer) GetUsersForRoleInDomain(name string, domain string) []string {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetUsersForRoleInDomain(name, domain)
}
// GetRolesForUserInDomain gets the roles that a user has inside a domain.
func (e *SyncedEnforcer) GetRolesForUserInDomain(name string, domain string) []string {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetRolesForUserInDomain(name, domain)
}
// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain.
func (e *SyncedEnforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string {
e.m.RLock()
defer e.m.RUnlock()
return e.Enforcer.GetPermissionsForUserInDomain(user, domain)
}
// AddRoleForUserInDomain adds a role for a user inside a domain.
// Returns false if the user already has the role (aka not affected).
func (e *SyncedEnforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.AddRoleForUserInDomain(user, role, domain)
}
// DeleteRoleForUserInDomain deletes a role for a user inside a domain.
// Returns false if the user does not have the role (aka not affected).
func (e *SyncedEnforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteRoleForUserInDomain(user, role, domain)
}
// DeleteRolesForUserInDomain deletes all roles for a user inside a domain.
// Returns false if the user does not have any roles (aka not affected).
func (e *SyncedEnforcer) DeleteRolesForUserInDomain(user string, domain string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteRolesForUserInDomain(user, domain)
}
// DeleteDomains deletes domains from the model.
// Returns false if the domain does not exist (aka not affected).
func (e *SyncedEnforcer) DeleteDomains(domains ...string) (bool, error) {
e.m.Lock()
defer e.m.Unlock()
return e.Enforcer.DeleteDomains(domains...)
}
================================================
FILE: rbac_api_with_domains_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sort"
"testing"
"github.com/casbin/casbin/v3/util"
)
// testGetUsersInDomain: Add by Gordon.
func testGetUsersInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {
t.Helper()
myRes := e.GetUsersForRoleInDomain(name, domain)
t.Log("Users for ", name, " under ", domain, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Users for ", name, " under ", domain, ": ", myRes, ", supposed to be ", res)
}
}
func testGetRolesInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {
t.Helper()
myRes := e.GetRolesForUserInDomain(name, domain)
t.Log("Roles for ", name, " under ", domain, ": ", myRes)
if !util.SetEquals(res, myRes) {
t.Error("Roles for ", name, " under ", domain, ": ", myRes, ", supposed to be ", res)
}
}
func TestGetImplicitRolesForDomainUser(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_hierarchy_with_domains_policy.csv")
// This is only able to retrieve the first level of roles.
testGetRolesInDomain(t, e, "alice", "domain1", []string{"role:global_admin"})
// Retrieve all inherit roles. It supports domains as well.
testGetImplicitRolesInDomain(t, e, "alice", "domain1", []string{"role:global_admin", "role:reader", "role:writer"})
}
// TestUserAPIWithDomains: Add by Gordon.
func TestUserAPIWithDomains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetUsers(t, e, []string{"alice"}, "admin", "domain1")
testGetUsersInDomain(t, e, "admin", "domain1", []string{"alice"})
testGetUsers(t, e, []string{}, "non_exist", "domain1")
testGetUsersInDomain(t, e, "non_exist", "domain1", []string{})
testGetUsers(t, e, []string{"bob"}, "admin", "domain2")
testGetUsersInDomain(t, e, "admin", "domain2", []string{"bob"})
testGetUsers(t, e, []string{}, "non_exist", "domain2")
testGetUsersInDomain(t, e, "non_exist", "domain2", []string{})
_, _ = e.DeleteRoleForUserInDomain("alice", "admin", "domain1")
_, _ = e.AddRoleForUserInDomain("bob", "admin", "domain1")
testGetUsers(t, e, []string{"bob"}, "admin", "domain1")
testGetUsersInDomain(t, e, "admin", "domain1", []string{"bob"})
testGetUsers(t, e, []string{}, "non_exist", "domain1")
testGetUsersInDomain(t, e, "non_exist", "domain1", []string{})
testGetUsers(t, e, []string{"bob"}, "admin", "domain2")
testGetUsersInDomain(t, e, "admin", "domain2", []string{"bob"})
testGetUsers(t, e, []string{}, "non_exist", "domain2")
testGetUsersInDomain(t, e, "non_exist", "domain2", []string{})
}
func TestRoleAPIWithDomains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetRoles(t, e, []string{"admin"}, "alice", "domain1")
testGetRolesInDomain(t, e, "alice", "domain1", []string{"admin"})
testGetRoles(t, e, []string{}, "bob", "domain1")
testGetRolesInDomain(t, e, "bob", "domain1", []string{})
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRolesInDomain(t, e, "admin", "domain1", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRolesInDomain(t, e, "non_exist", "domain1", []string{})
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRolesInDomain(t, e, "alice", "domain2", []string{})
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRolesInDomain(t, e, "bob", "domain2", []string{"admin"})
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRolesInDomain(t, e, "admin", "domain2", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain2")
testGetRolesInDomain(t, e, "non_exist", "domain2", []string{})
_, _ = e.DeleteRoleForUserInDomain("alice", "admin", "domain1")
_, _ = e.AddRoleForUserInDomain("bob", "admin", "domain1")
testGetRoles(t, e, []string{}, "alice", "domain1")
testGetRolesInDomain(t, e, "alice", "domain1", []string{})
testGetRoles(t, e, []string{"admin"}, "bob", "domain1")
testGetRolesInDomain(t, e, "bob", "domain1", []string{"admin"})
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRolesInDomain(t, e, "admin", "domain1", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRolesInDomain(t, e, "non_exist", "domain1", []string{})
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRolesInDomain(t, e, "alice", "domain2", []string{})
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRolesInDomain(t, e, "bob", "domain2", []string{"admin"})
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRolesInDomain(t, e, "admin", "domain2", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain2")
testGetRolesInDomain(t, e, "non_exist", "domain2", []string{})
_, _ = e.AddRoleForUserInDomain("alice", "admin", "domain1")
_, _ = e.DeleteRolesForUserInDomain("bob", "domain1")
testGetRoles(t, e, []string{"admin"}, "alice", "domain1")
testGetRolesInDomain(t, e, "alice", "domain1", []string{"admin"})
testGetRoles(t, e, []string{}, "bob", "domain1")
testGetRolesInDomain(t, e, "bob", "domain1", []string{})
testGetRoles(t, e, []string{}, "admin", "domain1")
testGetRolesInDomain(t, e, "admin", "domain1", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain1")
testGetRolesInDomain(t, e, "non_exist", "domain1", []string{})
testGetRoles(t, e, []string{}, "alice", "domain2")
testGetRolesInDomain(t, e, "alice", "domain2", []string{})
testGetRoles(t, e, []string{"admin"}, "bob", "domain2")
testGetRolesInDomain(t, e, "bob", "domain2", []string{"admin"})
testGetRoles(t, e, []string{}, "admin", "domain2")
testGetRolesInDomain(t, e, "admin", "domain2", []string{})
testGetRoles(t, e, []string{}, "non_exist", "domain2")
testGetRolesInDomain(t, e, "non_exist", "domain2", []string{})
}
func testGetPermissionsInDomain(t *testing.T, e *Enforcer, name string, domain string, res [][]string) {
t.Helper()
myRes := e.GetPermissionsForUserInDomain(name, domain)
t.Log("Permissions for ", name, " under ", domain, ": ", myRes)
if !util.Array2DEquals(res, myRes) {
t.Error("Permissions for ", name, " under ", domain, ": ", myRes, ", supposed to be ", res)
}
}
func TestPermissionAPIInDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetPermissionsInDomain(t, e, "alice", "domain1", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}})
testGetPermissionsInDomain(t, e, "bob", "domain1", [][]string{})
testGetPermissionsInDomain(t, e, "admin", "domain1", [][]string{{"admin", "domain1", "data1", "read"}, {"admin", "domain1", "data1", "write"}})
testGetPermissionsInDomain(t, e, "non_exist", "domain1", [][]string{})
testGetPermissionsInDomain(t, e, "alice", "domain2", [][]string{})
testGetPermissionsInDomain(t, e, "bob", "domain2", [][]string{{"admin", "domain2", "data2", "read"}, {"admin", "domain2", "data2", "write"}})
testGetPermissionsInDomain(t, e, "admin", "domain2", [][]string{{"admin", "domain2", "data2", "read"}, {"admin", "domain2", "data2", "write"}})
testGetPermissionsInDomain(t, e, "non_exist", "domain2", [][]string{})
}
func testGetDomainsForUser(t *testing.T, e *Enforcer, res []string, user string) {
t.Helper()
myRes, _ := e.GetDomainsForUser(user)
sort.Strings(myRes)
sort.Strings(res)
if !util.SetEquals(res, myRes) {
t.Error("domains for user: ", user, ": ", myRes, ", supposed to be ", res)
}
}
func TestGetDomainsForUser(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy2.csv")
testGetDomainsForUser(t, e, []string{"domain1", "domain2"}, "alice")
testGetDomainsForUser(t, e, []string{"domain2", "domain3"}, "bob")
testGetDomainsForUser(t, e, []string{"domain3"}, "user")
}
func testGetAllUsersByDomain(t *testing.T, e *Enforcer, domain string, expected []string) {
users, _ := e.GetAllUsersByDomain(domain)
if !util.SetEquals(users, expected) {
t.Errorf("users in %s: %v, supposed to be %v\n", domain, users, expected)
}
}
func TestGetAllUsersByDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetAllUsersByDomain(t, e, "domain1", []string{"alice", "admin"})
testGetAllUsersByDomain(t, e, "domain2", []string{"bob", "admin"})
}
func testDeleteAllUsersByDomain(t *testing.T, domain string, expectedPolicy, expectedGroupingPolicy [][]string) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
_, _ = e.DeleteAllUsersByDomain(domain)
policy, err := e.GetPolicy()
if err != nil {
t.Error(err)
}
if !util.Array2DEquals(policy, expectedPolicy) {
t.Errorf("policy in %s: %v, supposed to be %v\n", domain, policy, expectedPolicy)
}
policies, err := e.GetGroupingPolicy()
if err != nil {
t.Error(err)
}
if !util.Array2DEquals(policies, expectedGroupingPolicy) {
t.Errorf("grouping policy in %s: %v, supposed to be %v\n", domain, policies, expectedGroupingPolicy)
}
}
func TestDeleteAllUsersByDomain(t *testing.T) {
testDeleteAllUsersByDomain(t, "domain1", [][]string{
{"admin", "domain2", "data2", "read"},
{"admin", "domain2", "data2", "write"},
}, [][]string{
{"bob", "admin", "domain2"},
})
testDeleteAllUsersByDomain(t, "domain2", [][]string{
{"admin", "domain1", "data1", "read"},
{"admin", "domain1", "data1", "write"},
}, [][]string{
{"alice", "admin", "domain1"},
})
}
// testGetAllDomains tests GetAllDomains().
func testGetAllDomains(t *testing.T, e *Enforcer, res []string) {
t.Helper()
myRes, _ := e.GetAllDomains()
sort.Strings(myRes)
sort.Strings(res)
if !util.ArrayEquals(res, myRes) {
t.Error("domains: ", myRes, ", supposed to be ", res)
}
}
func TestGetAllDomains(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetAllDomains(t, e, []string{"domain1", "domain2"})
}
func testGetAllRolesByDomain(t *testing.T, e *Enforcer, domain string, expected []string) {
roles, _ := e.GetAllRolesByDomain(domain)
if !util.SetEquals(roles, expected) {
t.Errorf("roles in %s: %v, supposed to be %v\n", domain, roles, expected)
}
}
func TestGetAllRolesByDomain(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
testGetAllRolesByDomain(t, e, "domain1", []string{"admin"})
testGetAllRolesByDomain(t, e, "domain2", []string{"admin"})
e, _ = NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy2.csv")
testGetAllRolesByDomain(t, e, "domain1", []string{"admin"})
testGetAllRolesByDomain(t, e, "domain2", []string{"admin"})
testGetAllRolesByDomain(t, e, "domain3", []string{"user"})
}
func testDeleteDomains(t *testing.T, domains []string, expectedPolicy, expectedGroupingPolicy [][]string, expectedDomains []string) {
e, _ := NewEnforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv")
_, _ = e.DeleteDomains(domains...)
policy, err := e.GetPolicy()
if err != nil {
t.Error(err)
}
if !util.Array2DEquals(policy, expectedPolicy) {
t.Errorf("policy after deleting domains %v: %v, supposed to be %v\n", domains, policy, expectedPolicy)
}
policies, err := e.GetGroupingPolicy()
if err != nil {
t.Error(err)
}
if !util.Array2DEquals(policies, expectedGroupingPolicy) {
t.Errorf("grouping policy after deleting domains %v: %v, supposed to be %v\n", domains, policies, expectedGroupingPolicy)
}
domainsAfterRemoval, _ := e.GetAllDomains()
if !util.SetEquals(domainsAfterRemoval, expectedDomains) {
t.Errorf("domains after deleting %v: %v, supposed to be %v\n", domains, domainsAfterRemoval, expectedDomains)
}
}
func TestDeleteDomains(t *testing.T) {
testDeleteDomains(t, []string{"domain1"}, [][]string{
{"admin", "domain2", "data2", "read"},
{"admin", "domain2", "data2", "write"},
}, [][]string{
{"bob", "admin", "domain2"},
}, []string{"domain2"})
testDeleteDomains(t, []string{"domain2"}, [][]string{
{"admin", "domain1", "data1", "read"},
{"admin", "domain1", "data1", "write"},
}, [][]string{
{"alice", "admin", "domain1"},
}, []string{"domain1"})
testDeleteDomains(t, []string{}, [][]string{}, [][]string{}, []string{})
}
// TestGetRolesForUserInDomainWithConditionalFunctions.
func TestGetRolesForUserInDomainWithConditionalFunctions(t *testing.T) {
modelText := "examples/rbac_with_domains_conditional_model.conf"
policyText := "examples/rbac_with_domains_conditional_policy.csv"
e, err := NewEnforcer(modelText, policyText)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Test without conditional functions
t.Run("WithoutConditionalFunctions", func(t *testing.T) {
roles := e.GetRolesForUserInDomain("alice", "domain1")
expected := []string{"test1"}
if !util.SetEquals(roles, expected) {
t.Errorf("Expected roles %v, got %v", expected, roles)
}
})
t.Run("WithConditionalFunctions", func(t *testing.T) {
e2, err := NewEnforcer(modelText, policyText)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Add time-based conditional functions
g, err := e2.GetNamedGroupingPolicy("g")
if err != nil {
t.Fatalf("Failed to get grouping policy: %v", err)
}
for _, gp := range g {
if len(gp) >= 4 {
e2.AddNamedDomainLinkConditionFunc("g", gp[0], gp[1], gp[2], util.TimeMatchFunc)
e2.SetNamedDomainLinkConditionFuncParams("g", gp[0], gp[1], gp[2], "_", gp[4])
}
}
roles := e2.GetRolesForUserInDomain("alice", "domain1")
if roles == nil {
t.Error("GetRolesForUserInDomain should not return nil, even with conditional functions")
}
roles = e2.GetRolesForUserInDomain("bob", "domain2")
if roles == nil {
t.Error("GetRolesForUserInDomain should not return nil for bob, even with conditional functions")
}
})
t.Run("WithAlwaysTrueCondition", func(t *testing.T) {
e3, err := NewEnforcer(modelText, policyText)
if err != nil {
t.Fatalf("Failed to create enforcer: %v", err)
}
// Use always-true condition function
alwaysTrueFunc := func(params ...string) (bool, error) {
return true, nil
}
g, err := e3.GetNamedGroupingPolicy("g")
if err != nil {
t.Fatalf("Failed to get grouping policy: %v", err)
}
for _, gp := range g {
if len(gp) >= 4 {
e3.AddNamedDomainLinkConditionFunc("g", gp[0], gp[1], gp[2], alwaysTrueFunc)
}
}
roles := e3.GetRolesForUserInDomain("alice", "domain1")
expected := []string{"test1"}
if !util.SetEquals(roles, expected) {
t.Errorf("Expected roles %v, got %v", expected, roles)
}
roles = e3.GetRolesForUserInDomain("bob", "domain2")
expected = []string{"qa1"}
if !util.SetEquals(roles, expected) {
t.Errorf("Expected roles %v, got %v", expected, roles)
}
})
}
================================================
FILE: role_manager_b_test.go
================================================
package casbin
import (
"fmt"
"testing"
"github.com/casbin/casbin/v3/util"
)
func BenchmarkRoleManagerSmall(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// Do not rebuild the role inheritance relations for every AddGroupingPolicy() call.
e.EnableAutoBuildRoleLinks(false)
// 100 roles, 10 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 100; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 1000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
rm := e.GetRoleManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 100; j++ {
_, _ = rm.HasLink("user501", fmt.Sprintf("group%d", j))
}
}
}
func BenchmarkRoleManagerMedium(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// Do not rebuild the role inheritance relations for every AddGroupingPolicy() call.
e.EnableAutoBuildRoleLinks(false)
// 1000 roles, 100 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 1000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 10000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
err = e.BuildRoleLinks()
if err != nil {
b.Fatal(err)
}
rm := e.GetRoleManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
_, _ = rm.HasLink("user501", fmt.Sprintf("group%d", j))
}
}
}
func BenchmarkRoleManagerLarge(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_model.conf")
// 10000 roles, 1000 resources.
pPolicies := make([][]string, 0)
for i := 0; i < 10000; i++ {
pPolicies = append(pPolicies, []string{fmt.Sprintf("group%d", i), fmt.Sprintf("data%d", i/10), "read"})
}
_, err := e.AddPolicies(pPolicies)
if err != nil {
b.Fatal(err)
}
// 100000 users.
gPolicies := make([][]string, 0)
for i := 0; i < 100000; i++ {
gPolicies = append(gPolicies, []string{fmt.Sprintf("user%d", i), fmt.Sprintf("group%d", i/10)})
}
_, err = e.AddGroupingPolicies(gPolicies)
if err != nil {
b.Fatal(err)
}
rm := e.GetRoleManager()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < 10000; j++ {
_, _ = rm.HasLink("user501", fmt.Sprintf("group%d", j))
}
}
}
func BenchmarkBuildRoleLinksWithPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = e.BuildRoleLinks()
}
}
func BenchmarkBuildRoleLinksWithDomainPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = e.BuildRoleLinks()
}
}
func BenchmarkBuildRoleLinksWithPatternAndDomainPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = e.BuildRoleLinks()
}
}
func BenchmarkHasLinkWithPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
rm := e.rmMap["g"]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
}
}
func BenchmarkHasLinkWithDomainPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
rm := e.rmMap["g"]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
}
}
func BenchmarkHasLinkWithPatternAndDomainPatternLarge(b *testing.B) {
e, _ := NewEnforcer("examples/performance/rbac_with_pattern_large_scale_model.conf", "examples/performance/rbac_with_pattern_large_scale_policy.csv")
e.AddNamedMatchingFunc("g", "", util.KeyMatch4)
e.AddNamedDomainMatchingFunc("g", "", util.KeyMatch4)
rm := e.rmMap["g"]
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = rm.HasLink("staffUser1001", "staff001", "/orgs/1/sites/site001")
}
}
// BenchmarkConcurrentHasLinkWithMatching benchmarks concurrent HasLink performance with matching functions.
// Performance test for concurrent access with temporary role creation.
func BenchmarkConcurrentHasLinkWithMatching(b *testing.B) {
e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
e.AddNamedMatchingFunc("g2", "keyMatch2", util.KeyMatch2)
rm := e.GetRoleManager()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = rm.HasLink("alice", "/book/123")
}
})
}
================================================
FILE: syntax_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
// TestSyntaxMatcher tests that patterns like "p." inside quoted strings
// are not incorrectly escaped by EscapeAssertion.
// This addresses the bug where matchers containing strings like "a.p.p.l.e"
// would have the ".p." pattern incorrectly replaced with "_p_".
func TestSyntaxMatcher(t *testing.T) {
e, err := NewEnforcer("examples/syntax_matcher_model.conf")
if err != nil {
t.Fatalf("Error: %v\n", err)
}
// The matcher in syntax_matcher_model.conf is: m = r.sub == "a.p.p.l.e"
// This should match when r.sub is exactly "a.p.p.l.e"
testEnforce(t, e, "a.p.p.l.e", "file", "read", true)
testEnforce(t, e, "other", "file", "read", false)
}
================================================
FILE: transaction.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software.
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"errors"
"sync"
"time"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
const (
// Default timeout duration for lock acquisition.
defaultLockTimeout = 30 * time.Second
)
// Transaction represents a Casbin transaction.
// It provides methods to perform policy operations within a transaction.
// and commit or rollback all changes atomically.
type Transaction struct {
id string // Unique transaction identifier.
enforcer *TransactionalEnforcer // Reference to the transactional enforcer.
buffer *TransactionBuffer // Buffer for policy operations.
txContext persist.TransactionContext // Database transaction context.
ctx context.Context // Context for the transaction.
baseVersion int64 // Model version at transaction start.
committed bool // Whether the transaction has been committed.
rolledBack bool // Whether the transaction has been rolled back.
startTime time.Time // Transaction start timestamp.
mutex sync.RWMutex // Protects transaction state.
}
// AddPolicy adds a policy within the transaction.
// The policy is buffered and will be applied when the transaction is committed.
func (tx *Transaction) AddPolicy(params ...interface{}) (bool, error) {
return tx.AddNamedPolicy("p", params...)
}
// buildRuleFromParams converts parameters to a rule slice.
func (tx *Transaction) buildRuleFromParams(params ...interface{}) []string {
if len(params) == 1 {
if strSlice, ok := params[0].([]string); ok {
rule := make([]string, 0, len(strSlice))
rule = append(rule, strSlice...)
return rule
}
}
rule := make([]string, 0, len(params))
for _, param := range params {
rule = append(rule, param.(string))
}
return rule
}
// checkTransactionStatus checks if the transaction is active.
func (tx *Transaction) checkTransactionStatus() error {
if tx.committed || tx.rolledBack {
return errors.New("transaction is not active")
}
return nil
}
// AddNamedPolicy adds a named policy within the transaction.
// The policy is buffered and will be applied when the transaction is committed.
func (tx *Transaction) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
rule := tx.buildRuleFromParams(params...)
// Check if policy already exists in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
if hasPolicy || err != nil {
return false, err
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationAdd,
Section: "p",
PolicyType: ptype,
Rules: [][]string{rule},
}
tx.buffer.AddOperation(op)
return true, nil
}
// AddPolicies adds multiple policies within the transaction.
func (tx *Transaction) AddPolicies(rules [][]string) (bool, error) {
return tx.AddNamedPolicies("p", rules)
}
// AddNamedPolicies adds multiple named policies within the transaction.
func (tx *Transaction) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
if len(rules) == 0 {
return false, nil
}
// Check if any policies already exist in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
var validRules [][]string
for _, rule := range rules {
hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
if err != nil {
return false, err
}
if !hasPolicy {
validRules = append(validRules, rule)
}
}
if len(validRules) == 0 {
return false, nil
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationAdd,
Section: "p",
PolicyType: ptype,
Rules: validRules,
}
tx.buffer.AddOperation(op)
return true, nil
}
// RemovePolicy removes a policy within the transaction.
func (tx *Transaction) RemovePolicy(params ...interface{}) (bool, error) {
return tx.RemoveNamedPolicy("p", params...)
}
// RemoveNamedPolicy removes a named policy within the transaction.
func (tx *Transaction) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
rule := tx.buildRuleFromParams(params...)
// Check if policy exists in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
if !hasPolicy || err != nil {
return false, err
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationRemove,
Section: "p",
PolicyType: ptype,
Rules: [][]string{rule},
}
tx.buffer.AddOperation(op)
return true, nil
}
// RemovePolicies removes multiple policies within the transaction.
func (tx *Transaction) RemovePolicies(rules [][]string) (bool, error) {
return tx.RemoveNamedPolicies("p", rules)
}
// RemoveNamedPolicies removes multiple named policies within the transaction.
func (tx *Transaction) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
if len(rules) == 0 {
return false, nil
}
// Check if policies exist in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
var validRules [][]string
for _, rule := range rules {
hasPolicy, err := bufferedModel.HasPolicy("p", ptype, rule)
if err != nil {
return false, err
}
if hasPolicy {
validRules = append(validRules, rule)
}
}
if len(validRules) == 0 {
return false, nil
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationRemove,
Section: "p",
PolicyType: ptype,
Rules: validRules,
}
tx.buffer.AddOperation(op)
return true, nil
}
// UpdatePolicy updates a policy within the transaction.
func (tx *Transaction) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {
return tx.UpdateNamedPolicy("p", oldPolicy, newPolicy)
}
// UpdateNamedPolicy updates a named policy within the transaction.
func (tx *Transaction) UpdateNamedPolicy(ptype string, oldPolicy []string, newPolicy []string) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
// Check if old policy exists and new policy doesn't exist.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
hasOldPolicy, err := bufferedModel.HasPolicy("p", ptype, oldPolicy)
if err != nil {
return false, err
}
if !hasOldPolicy {
return false, nil
}
hasNewPolicy, errNew := bufferedModel.HasPolicy("p", ptype, newPolicy)
if errNew != nil {
return false, errNew
}
if hasNewPolicy {
return false, nil
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationUpdate,
Section: "p",
PolicyType: ptype,
Rules: [][]string{newPolicy},
OldRules: [][]string{oldPolicy},
}
tx.buffer.AddOperation(op)
return true, nil
}
// AddGroupingPolicy adds a grouping policy within the transaction.
func (tx *Transaction) AddGroupingPolicy(params ...interface{}) (bool, error) {
return tx.AddNamedGroupingPolicy("g", params...)
}
// AddNamedGroupingPolicy adds a named grouping policy within the transaction.
func (tx *Transaction) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
rule := tx.buildRuleFromParams(params...)
// Check if grouping policy already exists in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
hasPolicy, err := bufferedModel.HasPolicy("g", ptype, rule)
if hasPolicy || err != nil {
return false, err
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationAdd,
Section: "g",
PolicyType: ptype,
Rules: [][]string{rule},
}
tx.buffer.AddOperation(op)
return true, nil
}
// RemoveGroupingPolicy removes a grouping policy within the transaction.
func (tx *Transaction) RemoveGroupingPolicy(params ...interface{}) (bool, error) {
return tx.RemoveNamedGroupingPolicy("g", params...)
}
// RemoveNamedGroupingPolicy removes a named grouping policy within the transaction.
func (tx *Transaction) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {
tx.mutex.Lock()
defer tx.mutex.Unlock()
if err := tx.checkTransactionStatus(); err != nil {
return false, err
}
rule := tx.buildRuleFromParams(params...)
// Check if grouping policy exists in the buffered model.
bufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return false, err
}
hasPolicy, err := bufferedModel.HasPolicy("g", ptype, rule)
if !hasPolicy || err != nil {
return false, err
}
// Add operation to buffer.
op := persist.PolicyOperation{
Type: persist.OperationRemove,
Section: "g",
PolicyType: ptype,
Rules: [][]string{rule},
}
tx.buffer.AddOperation(op)
return true, nil
}
// GetBufferedModel returns the model as it would look after applying all buffered operations.
// This is useful for preview or validation purposes within the transaction.
func (tx *Transaction) GetBufferedModel() (model.Model, error) {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
if err := tx.checkTransactionStatus(); err != nil {
return nil, err
}
return tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
}
// HasOperations returns true if the transaction has any buffered operations.
func (tx *Transaction) HasOperations() bool {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
return tx.buffer.HasOperations()
}
// OperationCount returns the number of buffered operations in the transaction.
func (tx *Transaction) OperationCount() int {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
return tx.buffer.OperationCount()
}
// tryLockWithTimeout attempts to acquire the lock within the specified timeout period.
func tryLockWithTimeout(lock *sync.Mutex, startTime time.Time, maxWait time.Duration) bool {
// Calculate remaining wait time based on transaction start time.
remainingTime := maxWait - time.Since(startTime)
if remainingTime <= 0 {
return false
}
// Create a context with timeout for lock acquisition.
ctx, cancel := context.WithTimeout(context.Background(), remainingTime)
defer cancel()
// Use channel for timeout control.
done := make(chan bool, 1)
go func() {
lock.Lock()
done <- true
}()
// Wait for either lock acquisition or timeout.
select {
case <-done:
return true
case <-ctx.Done():
return false
}
}
================================================
FILE: transaction_buffer.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"sync"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// TransactionBuffer holds all policy changes made within a transaction.
// It maintains a list of operations and a snapshot of the model state
// at the beginning of the transaction.
type TransactionBuffer struct {
operations []persist.PolicyOperation // Buffered operations
modelSnapshot model.Model // Model state at transaction start
mutex sync.RWMutex // Protects concurrent access
}
// NewTransactionBuffer creates a new transaction buffer with a model snapshot.
// The snapshot represents the state of the model at the beginning of the transaction.
func NewTransactionBuffer(baseModel model.Model) *TransactionBuffer {
return &TransactionBuffer{
operations: make([]persist.PolicyOperation, 0),
modelSnapshot: baseModel.Copy(),
}
}
// AddOperation adds a policy operation to the buffer.
// This operation will be applied when the transaction is committed.
func (tb *TransactionBuffer) AddOperation(op persist.PolicyOperation) {
tb.mutex.Lock()
defer tb.mutex.Unlock()
tb.operations = append(tb.operations, op)
}
// GetOperations returns all buffered operations.
// Returns a copy to prevent external modifications.
func (tb *TransactionBuffer) GetOperations() []persist.PolicyOperation {
tb.mutex.RLock()
defer tb.mutex.RUnlock()
// Return a copy to prevent external modifications.
result := make([]persist.PolicyOperation, len(tb.operations))
copy(result, tb.operations)
return result
}
// Clear removes all buffered operations.
// This is typically called after a successful commit or rollback.
func (tb *TransactionBuffer) Clear() {
tb.mutex.Lock()
defer tb.mutex.Unlock()
tb.operations = tb.operations[:0]
}
// GetModelSnapshot returns the model snapshot taken at transaction start.
// This represents the original state before any transaction operations.
func (tb *TransactionBuffer) GetModelSnapshot() model.Model {
tb.mutex.RLock()
defer tb.mutex.RUnlock()
return tb.modelSnapshot.Copy()
}
// ApplyOperationsToModel applies all buffered operations to a model and returns the result.
// This simulates what the model would look like after all operations are applied.
// It's used for validation and preview purposes within the transaction.
func (tb *TransactionBuffer) ApplyOperationsToModel(baseModel model.Model) (model.Model, error) {
tb.mutex.RLock()
defer tb.mutex.RUnlock()
resultModel := baseModel.Copy()
for _, op := range tb.operations {
switch op.Type {
case persist.OperationAdd:
for _, rule := range op.Rules {
if err := resultModel.AddPolicy(op.Section, op.PolicyType, rule); err != nil {
return nil, err
}
}
case persist.OperationRemove:
for _, rule := range op.Rules {
if _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, rule); err != nil {
return nil, err
}
}
case persist.OperationUpdate:
// For update operations, remove old rules and add new ones.
for i, oldRule := range op.OldRules {
if i < len(op.Rules) {
if _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, oldRule); err != nil {
return nil, err
}
if err := resultModel.AddPolicy(op.Section, op.PolicyType, op.Rules[i]); err != nil {
return nil, err
}
}
}
}
}
return resultModel, nil
}
// HasOperations returns true if there are any buffered operations.
func (tb *TransactionBuffer) HasOperations() bool {
tb.mutex.RLock()
defer tb.mutex.RUnlock()
return len(tb.operations) > 0
}
// OperationCount returns the number of buffered operations.
func (tb *TransactionBuffer) OperationCount() int {
tb.mutex.RLock()
defer tb.mutex.RUnlock()
return len(tb.operations)
}
================================================
FILE: transaction_commit.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"errors"
"sync/atomic"
"github.com/casbin/casbin/v3/persist"
)
// Commit commits the transaction using a two-phase commit protocol.
// Phase 1: Apply all operations to the database
// Phase 2: Apply changes to the in-memory model and rebuild role links.
func (tx *Transaction) Commit() error {
// Try to acquire the commit lock with timeout.
if !tryLockWithTimeout(&tx.enforcer.commitLock, tx.startTime, defaultLockTimeout) {
_ = tx.txContext.Rollback()
tx.enforcer.activeTransactions.Delete(tx.id)
return errors.New("transaction timeout: failed to acquire lock")
}
defer tx.enforcer.commitLock.Unlock()
tx.mutex.Lock()
defer tx.mutex.Unlock()
if tx.committed {
return errors.New("transaction already committed")
}
if tx.rolledBack {
return errors.New("transaction already rolled back")
}
// First check if model version has changed.
currentVersion := atomic.LoadInt64(&tx.enforcer.modelVersion)
if currentVersion != tx.baseVersion {
// Model has been modified, need to check for conflicts.
detector := NewConflictDetector(
tx.buffer.GetModelSnapshot(),
tx.enforcer.model,
tx.buffer.GetOperations(),
)
if err := detector.DetectConflicts(); err != nil {
_ = tx.txContext.Rollback()
tx.enforcer.activeTransactions.Delete(tx.id)
return err
}
}
// If no operations, just commit the database transaction and clear state.
if !tx.buffer.HasOperations() {
if err := tx.txContext.Commit(); err != nil {
return err
}
tx.committed = true
tx.enforcer.activeTransactions.Delete(tx.id)
return nil
}
// Phase 1: Apply all buffered operations to the database
if err := tx.applyOperationsToDatabase(); err != nil {
// Rollback database transaction on failure.
_ = tx.txContext.Rollback()
tx.enforcer.activeTransactions.Delete(tx.id)
return err
}
// Commit database transaction.
if err := tx.txContext.Commit(); err != nil {
tx.enforcer.activeTransactions.Delete(tx.id)
return err
}
// Phase 2: Apply changes to the in-memory model
if err := tx.applyOperationsToModel(); err != nil {
// At this point, database is committed but model update failed.
// This is a critical error that should not happen in normal circumstances.
tx.enforcer.activeTransactions.Delete(tx.id)
return errors.New("critical error: database committed but model update failed: " + err.Error())
}
// Increment model version number.
atomic.AddInt64(&tx.enforcer.modelVersion, 1)
tx.committed = true
tx.enforcer.activeTransactions.Delete(tx.id)
return nil
}
// Rollback rolls back the transaction.
// This will rollback the database transaction and clear the transaction state.
func (tx *Transaction) Rollback() error {
// Try to acquire the commit lock with timeout.
if !tryLockWithTimeout(&tx.enforcer.commitLock, tx.startTime, defaultLockTimeout) {
tx.enforcer.activeTransactions.Delete(tx.id)
return errors.New("transaction timeout: failed to acquire lock for rollback")
}
defer tx.enforcer.commitLock.Unlock()
tx.mutex.Lock()
defer tx.mutex.Unlock()
if tx.committed {
return errors.New("transaction already committed")
}
if tx.rolledBack {
return errors.New("transaction already rolled back")
}
// Rollback database transaction.
if err := tx.txContext.Rollback(); err != nil {
return err
}
tx.rolledBack = true
tx.enforcer.activeTransactions.Delete(tx.id)
return nil
}
// applyOperationsToDatabase applies all buffered operations to the database.
func (tx *Transaction) applyOperationsToDatabase() error {
operations := tx.buffer.GetOperations()
txAdapter := tx.txContext.GetAdapter()
for _, op := range operations {
switch op.Type {
case persist.OperationAdd:
if err := tx.applyAddOperationToDatabase(txAdapter, op); err != nil {
return err
}
case persist.OperationRemove:
if err := tx.applyRemoveOperationToDatabase(txAdapter, op); err != nil {
return err
}
case persist.OperationUpdate:
if err := tx.applyUpdateOperationToDatabase(txAdapter, op); err != nil {
return err
}
}
}
return nil
}
// applyAddOperationToDatabase applies an add operation to the database.
func (tx *Transaction) applyAddOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {
if batchAdapter, ok := adapter.(persist.BatchAdapter); ok {
// Use batch operation if available.
return batchAdapter.AddPolicies(op.Section, op.PolicyType, op.Rules)
} else {
// Fall back to individual operations.
for _, rule := range op.Rules {
if err := adapter.AddPolicy(op.Section, op.PolicyType, rule); err != nil {
return err
}
}
}
return nil
}
// applyRemoveOperationToDatabase applies a remove operation to the database.
func (tx *Transaction) applyRemoveOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {
if batchAdapter, ok := adapter.(persist.BatchAdapter); ok {
// Use batch operation if available.
return batchAdapter.RemovePolicies(op.Section, op.PolicyType, op.Rules)
} else {
// Fall back to individual operations.
for _, rule := range op.Rules {
if err := adapter.RemovePolicy(op.Section, op.PolicyType, rule); err != nil {
return err
}
}
}
return nil
}
// applyUpdateOperationToDatabase applies an update operation to the database.
func (tx *Transaction) applyUpdateOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {
if updateAdapter, ok := adapter.(persist.UpdatableAdapter); ok {
// Use update operation if available.
return updateAdapter.UpdatePolicies(op.Section, op.PolicyType, op.OldRules, op.Rules)
}
// Fall back to remove + add.
for i, oldRule := range op.OldRules {
if err := adapter.RemovePolicy(op.Section, op.PolicyType, oldRule); err != nil {
return err
}
if err := adapter.AddPolicy(op.Section, op.PolicyType, op.Rules[i]); err != nil {
return err
}
}
return nil
}
// applyOperationsToModel applies all buffered operations to the in-memory model.
func (tx *Transaction) applyOperationsToModel() error {
// Create new model with all operations applied.
newModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())
if err != nil {
return err
}
// Replace the enforcer's model.
tx.enforcer.model = newModel
tx.enforcer.invalidateMatcherMap()
// Rebuild role links if necessary.
if tx.enforcer.autoBuildRoleLinks {
// Check if any operations involved grouping policies.
operations := tx.buffer.GetOperations()
needRoleRebuild := false
for _, op := range operations {
if op.Section == "g" {
needRoleRebuild = true
break
}
}
if needRoleRebuild {
if err := tx.enforcer.BuildRoleLinks(); err != nil {
return err
}
}
}
return nil
}
// IsCommitted returns true if the transaction has been committed.
func (tx *Transaction) IsCommitted() bool {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
return tx.committed
}
// IsRolledBack returns true if the transaction has been rolled back.
func (tx *Transaction) IsRolledBack() bool {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
return tx.rolledBack
}
// IsActive returns true if the transaction is still active (not committed or rolled back).
func (tx *Transaction) IsActive() bool {
tx.mutex.RLock()
defer tx.mutex.RUnlock()
return !tx.committed && !tx.rolledBack
}
================================================
FILE: transaction_conflict.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"fmt"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// ConflictError represents a transaction conflict error.
type ConflictError struct {
Operation persist.PolicyOperation
Reason string
}
func (e *ConflictError) Error() string {
return fmt.Sprintf("transaction conflict: %s for operation %v", e.Reason, e.Operation)
}
// ConflictDetector detects conflicts between transaction operations and current model state.
type ConflictDetector struct {
baseModel model.Model // Model snapshot when transaction started
currentModel model.Model // Current model state
operations []persist.PolicyOperation // Operations to be applied
}
// NewConflictDetector creates a new conflict detector instance.
func NewConflictDetector(baseModel, currentModel model.Model, operations []persist.PolicyOperation) *ConflictDetector {
return &ConflictDetector{
baseModel: baseModel,
currentModel: currentModel,
operations: operations,
}
}
// DetectConflicts checks for conflicts between the transaction operations and current model state.
// Returns nil if no conflicts are found, otherwise returns a ConflictError describing the conflict.
func (cd *ConflictDetector) DetectConflicts() error {
for _, op := range cd.operations {
var err error
switch op.Type {
case persist.OperationAdd:
// Add operations never conflict
continue
case persist.OperationRemove:
err = cd.detectRemoveConflict(op)
case persist.OperationUpdate:
err = cd.detectUpdateConflict(op)
}
if err != nil {
return err
}
}
return nil
}
// detectRemoveConflict checks for conflicts in remove operations.
func (cd *ConflictDetector) detectRemoveConflict(op persist.PolicyOperation) error {
for _, rule := range op.Rules {
// Check if policy existed in base model
baseHasPolicy, err := cd.baseModel.HasPolicy(op.Section, op.PolicyType, rule)
if err != nil {
return err
}
if !baseHasPolicy {
continue // Policy didn't exist when transaction started
}
// Check if policy still exists in current model
currentHasPolicy, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, rule)
if err != nil {
return err
}
if !currentHasPolicy {
return &ConflictError{
Operation: op,
Reason: "policy has been removed by another transaction",
}
}
}
return nil
}
// detectUpdateConflict checks for conflicts in update operations.
func (cd *ConflictDetector) detectUpdateConflict(op persist.PolicyOperation) error {
for i, oldRule := range op.OldRules {
if i >= len(op.Rules) {
break
}
newRule := op.Rules[i]
// Check if old policy still exists
oldExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, oldRule)
if err != nil {
return err
}
if !oldExists {
return &ConflictError{
Operation: op,
Reason: "policy to be updated no longer exists",
}
}
// Check if new policy already exists
newExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, newRule)
if err != nil {
return err
}
if newExists {
return &ConflictError{
Operation: op,
Reason: "target policy already exists",
}
}
}
return nil
}
================================================
FILE: transaction_test.go
================================================
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"context"
"errors"
"testing"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// MockTransactionalAdapter implements TransactionalAdapter interface for testing.
type MockTransactionalAdapter struct {
Enforcer *Enforcer
}
// MockTransactionContext implements TransactionContext interface for testing.
type MockTransactionContext struct {
adapter *MockTransactionalAdapter
committed bool
rolledBack bool
}
// NewMockTransactionalAdapter creates a new mock adapter.
func NewMockTransactionalAdapter() *MockTransactionalAdapter {
return &MockTransactionalAdapter{}
}
// LoadPolicy implements Adapter interface.
func (a *MockTransactionalAdapter) LoadPolicy(model model.Model) error {
return nil
}
// SavePolicy implements Adapter interface.
func (a *MockTransactionalAdapter) SavePolicy(model model.Model) error {
return nil
}
// AddPolicy implements Adapter interface.
func (a *MockTransactionalAdapter) AddPolicy(sec string, ptype string, rule []string) error {
return nil
}
// RemovePolicy implements Adapter interface.
func (a *MockTransactionalAdapter) RemovePolicy(sec string, ptype string, rule []string) error {
return nil
}
// RemoveFilteredPolicy implements Adapter interface.
func (a *MockTransactionalAdapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return nil
}
// BeginTransaction implements TransactionalAdapter interface.
func (a *MockTransactionalAdapter) BeginTransaction(ctx context.Context) (persist.TransactionContext, error) {
return &MockTransactionContext{adapter: a}, nil
}
// Commit implements TransactionContext interface.
func (tx *MockTransactionContext) Commit() error {
if tx.committed || tx.rolledBack {
return errors.New("transaction already finished")
}
tx.committed = true
return nil
}
// Rollback implements TransactionContext interface.
func (tx *MockTransactionContext) Rollback() error {
if tx.committed || tx.rolledBack {
return errors.New("transaction already finished")
}
tx.rolledBack = true
return nil
}
// GetAdapter implements TransactionContext interface.
func (tx *MockTransactionContext) GetAdapter() persist.Adapter {
return tx.adapter
}
// Test basic transaction functionality.
func TestTransactionBasicOperations(t *testing.T) {
adapter := NewMockTransactionalAdapter()
e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
if err != nil {
t.Fatalf("Failed to create transactional enforcer: %v", err)
}
adapter.Enforcer = e.Enforcer
ctx := context.Background()
// Begin transaction.
tx, err := e.BeginTransaction(ctx)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
// Add policies in transaction.
ok, err := tx.AddPolicy("alice", "data1", "read")
if !ok || err != nil {
t.Fatalf("Failed to add policy in transaction: %v", err)
}
ok, err = tx.AddPolicy("bob", "data2", "write")
if !ok || err != nil {
t.Fatalf("Failed to add policy in transaction: %v", err)
}
// Commit transaction.
if err := tx.Commit(); err != nil {
t.Fatalf("Failed to commit transaction: %v", err)
}
// Verify transaction was committed.
if !tx.IsCommitted() {
t.Error("Transaction should be committed")
}
}
// Test transaction rollback.
func TestTransactionRollback(t *testing.T) {
adapter := NewMockTransactionalAdapter()
e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
if err != nil {
t.Fatalf("Failed to create transactional enforcer: %v", err)
}
adapter.Enforcer = e.Enforcer
ctx := context.Background()
// Begin transaction.
tx, err := e.BeginTransaction(ctx)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
// Add policy in transaction.
ok, err := tx.AddPolicy("alice", "data1", "read")
if !ok || err != nil {
t.Fatalf("Failed to add policy in transaction: %v", err)
}
// Rollback transaction.
if err := tx.Rollback(); err != nil {
t.Fatalf("Failed to rollback transaction: %v", err)
}
// Verify transaction was rolled back.
if !tx.IsRolledBack() {
t.Error("Transaction should be rolled back")
}
}
// Test concurrent transactions.
func TestConcurrentTransactions(t *testing.T) {
adapter := NewMockTransactionalAdapter()
e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
if err != nil {
t.Fatalf("Failed to create transactional enforcer: %v", err)
}
adapter.Enforcer = e.Enforcer
ctx := context.Background()
// Start first transaction
tx1, err := e.BeginTransaction(ctx)
if err != nil {
t.Fatalf("Failed to begin transaction 1: %v", err)
}
// Add policy in first transaction
ok, err := tx1.AddPolicy("alice", "data1", "read")
if !ok || err != nil {
t.Fatalf("Failed to add policy in transaction 1: %v", err)
}
// Start second transaction
tx2, err := e.BeginTransaction(ctx)
if err != nil {
t.Fatalf("Failed to begin transaction 2: %v", err)
}
// Add different policy in second transaction
ok, err = tx2.AddPolicy("bob", "data2", "write")
if !ok || err != nil {
t.Fatalf("Failed to add policy in transaction 2: %v", err)
}
// Commit first transaction
if err := tx1.Commit(); err != nil {
t.Fatalf("Failed to commit transaction 1: %v", err)
}
// Commit second transaction
if err := tx2.Commit(); err != nil {
t.Fatalf("Failed to commit transaction 2: %v", err)
}
// Verify transactions were committed
if !tx1.IsCommitted() {
t.Error("Transaction 1 should be committed")
}
if !tx2.IsCommitted() {
t.Error("Transaction 2 should be committed")
}
}
// Test transaction conflicts.
func TestTransactionConflicts(t *testing.T) {
adapter := NewMockTransactionalAdapter()
e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
if err != nil {
t.Fatalf("Failed to create transactional enforcer: %v", err)
}
adapter.Enforcer = e.Enforcer
ctx := context.Background()
// Test Case 1: Two transactions commit
t.Run("TwoTransactionsCommit", func(t *testing.T) {
tx1, _ := e.BeginTransaction(ctx)
tx2, _ := e.BeginTransaction(ctx)
// Commit both transactions
if err := tx1.Commit(); err != nil {
t.Fatalf("Failed to commit tx1: %v", err)
}
if err := tx2.Commit(); err != nil {
t.Fatalf("Failed to commit tx2: %v", err)
}
// Verify both transactions were committed
if !tx1.IsCommitted() {
t.Error("Transaction 1 should be committed")
}
if !tx2.IsCommitted() {
t.Error("Transaction 2 should be committed")
}
})
// Test Case 2: Transaction rollback
t.Run("TransactionRollback", func(t *testing.T) {
tx, _ := e.BeginTransaction(ctx)
// Rollback transaction
if err := tx.Rollback(); err != nil {
t.Fatalf("Failed to rollback transaction: %v", err)
}
// Verify transaction was rolled back
if !tx.IsRolledBack() {
t.Error("Transaction should be rolled back")
}
})
// Test Case 3: Cannot commit after rollback
t.Run("NoCommitAfterRollback", func(t *testing.T) {
tx, _ := e.BeginTransaction(ctx)
// Rollback transaction
if err := tx.Rollback(); err != nil {
t.Fatalf("Failed to rollback transaction: %v", err)
}
// Try to commit
if err := tx.Commit(); err == nil {
t.Error("Should not be able to commit after rollback")
}
})
}
// Test transaction buffer operations.
func TestTransactionBuffer(t *testing.T) {
adapter := NewMockTransactionalAdapter()
e, err := NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
if err != nil {
t.Fatalf("Failed to create transactional enforcer: %v", err)
}
adapter.Enforcer = e.Enforcer
ctx := context.Background()
tx, err := e.BeginTransaction(ctx)
if err != nil {
t.Fatalf("Failed to begin transaction: %v", err)
}
// Initially no operations.
if tx.HasOperations() {
t.Fatal("Transaction should have no operations initially")
}
if tx.OperationCount() != 0 {
t.Fatal("Operation count should be 0 initially")
}
// Add some operations.
tx.AddPolicy("alice", "data1", "read")
tx.AddPolicy("bob", "data2", "write")
if !tx.HasOperations() {
t.Fatal("Transaction should have operations")
}
if tx.OperationCount() != 2 {
t.Fatalf("Expected 2 operations, got %d", tx.OperationCount())
}
// Get buffered model.
bufferedModel, err := tx.GetBufferedModel()
if err != nil {
t.Fatalf("Failed to get buffered model: %v", err)
}
// Check that buffered model contains the policies.
hasPolicy, _ := bufferedModel.HasPolicy("p", "p", []string{"alice", "data1", "read"})
if !hasPolicy {
t.Fatal("Buffered model should contain the added policy")
}
tx.Rollback()
}
================================================
FILE: util/builtin_operators.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"errors"
"fmt"
"net"
"regexp"
"strings"
"sync"
"time"
"github.com/bmatcuk/doublestar/v4"
"github.com/casbin/casbin/v3/rbac"
"github.com/casbin/govaluate"
)
var (
keyMatch2Re = regexp.MustCompile(`:[^/]+`)
keyMatch3Re = regexp.MustCompile(`\{[^/]+\}`)
keyMatch4Re = regexp.MustCompile(`{([^/]+)}`)
keyMatch5Re = regexp.MustCompile(`\{[^/]+\}`)
keyGet2Re1 = regexp.MustCompile(`:[^/]+`)
keyGet3Re1 = regexp.MustCompile(`\{[^/]+?\}`) // non-greedy match of `{...}` to support multiple {} in `/.../`
reCache = map[string]*regexp.Regexp{}
reCacheMu = sync.RWMutex{}
)
func mustCompileOrGet(key string) *regexp.Regexp {
reCacheMu.RLock()
re, ok := reCache[key]
reCacheMu.RUnlock()
if !ok {
re = regexp.MustCompile(key)
reCacheMu.Lock()
reCache[key] = re
reCacheMu.Unlock()
}
return re
}
// validate the variadic parameter size and type as string.
func validateVariadicArgs(expectedLen int, args ...interface{}) error {
if len(args) != expectedLen {
return fmt.Errorf("expected %d arguments, but got %d", expectedLen, len(args))
}
for _, p := range args {
_, ok := p.(string)
if !ok {
return errors.New("argument must be a string")
}
}
return nil
}
// validate the variadic string parameter size.
func validateVariadicStringArgs(expectedLen int, args ...string) error {
if len(args) != expectedLen {
return fmt.Errorf("expected %d arguments, but got %d", expectedLen, len(args))
}
return nil
}
// KeyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
// For example, "/foo/bar" matches "/foo/*".
func KeyMatch(key1 string, key2 string) bool {
i := strings.Index(key2, "*")
if i == -1 {
return key1 == key2
}
if len(key1) > i {
return key1[:i] == key2[:i]
}
return key1 == key2[:i]
}
// KeyMatchFunc is the wrapper for KeyMatch.
func KeyMatchFunc(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyMatch", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyMatch(name1, name2), nil
}
// KeyGet returns the matched part
// For example, "/foo/bar/foo" matches "/foo/*"
// "bar/foo" will been returned.
func KeyGet(key1, key2 string) string {
i := strings.Index(key2, "*")
if i == -1 {
return ""
}
if len(key1) > i {
if key1[:i] == key2[:i] {
return key1[i:]
}
}
return ""
}
// KeyGetFunc is the wrapper for KeyGet.
func KeyGetFunc(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyGet", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyGet(name1, name2), nil
}
// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource".
func KeyMatch2(key1 string, key2 string) bool {
key2 = strings.Replace(key2, "/*", "/.*", -1)
key2 = keyMatch2Re.ReplaceAllString(key2, "$1[^/]+$2")
return RegexMatch(key1, "^"+key2+"$")
}
// KeyMatch2Func is the wrapper for KeyMatch2.
func KeyMatch2Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyMatch2", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyMatch2(name1, name2), nil
}
// KeyGet2 returns value matched pattern
// For example, "/resource1" matches "/:resource"
// if the pathVar == "resource", then "resource1" will be returned.
func KeyGet2(key1, key2 string, pathVar string) string {
key2 = strings.Replace(key2, "/*", "/.*", -1)
keys := keyGet2Re1.FindAllString(key2, -1)
key2 = keyGet2Re1.ReplaceAllString(key2, "$1([^/]+)$2")
key2 = "^" + key2 + "$"
re := mustCompileOrGet(key2)
values := re.FindAllStringSubmatch(key1, -1)
if len(values) == 0 {
return ""
}
for i, key := range keys {
if pathVar == key[1:] {
return values[0][i+1]
}
}
return ""
}
// KeyGet2Func is the wrapper for KeyGet2.
func KeyGet2Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(3, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyGet2", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
key := args[2].(string)
return KeyGet2(name1, name2, key), nil
}
// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
// For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}".
func KeyMatch3(key1 string, key2 string) bool {
key2 = strings.Replace(key2, "/*", "/.*", -1)
key2 = keyMatch3Re.ReplaceAllString(key2, "$1[^/]+$2")
return RegexMatch(key1, "^"+key2+"$")
}
// KeyMatch3Func is the wrapper for KeyMatch3.
func KeyMatch3Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyMatch3", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyMatch3(name1, name2), nil
}
// KeyGet3 returns value matched pattern
// For example, "project/proj_project1_admin/" matches "project/proj_{project}_admin/"
// if the pathVar == "project", then "project1" will be returned.
func KeyGet3(key1, key2 string, pathVar string) string {
key2 = strings.Replace(key2, "/*", "/.*", -1)
keys := keyGet3Re1.FindAllString(key2, -1)
key2 = keyGet3Re1.ReplaceAllString(key2, "$1([^/]+?)$2")
key2 = "^" + key2 + "$"
re := mustCompileOrGet(key2)
values := re.FindAllStringSubmatch(key1, -1)
if len(values) == 0 {
return ""
}
for i, key := range keys {
if pathVar == key[1:len(key)-1] {
return values[0][i+1]
}
}
return ""
}
// KeyGet3Func is the wrapper for KeyGet3.
func KeyGet3Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(3, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyGet3", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
key := args[2].(string)
return KeyGet3(name1, name2, key), nil
}
// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns:
// "/parent/123/child/123" matches "/parent/{id}/child/{id}"
// "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
// But KeyMatch3 will match both.
func KeyMatch4(key1 string, key2 string) bool {
key2 = strings.Replace(key2, "/*", "/.*", -1)
tokens := []string{}
re := keyMatch4Re
key2 = re.ReplaceAllStringFunc(key2, func(s string) string {
tokens = append(tokens, s[1:len(s)-1])
return "([^/]+)"
})
re = mustCompileOrGet("^" + key2 + "$")
matches := re.FindStringSubmatch(key1)
if matches == nil {
return false
}
matches = matches[1:]
if len(tokens) != len(matches) {
panic(errors.New("KeyMatch4: number of tokens is not equal to number of values"))
}
values := map[string]string{}
for key, token := range tokens {
if _, ok := values[token]; !ok {
values[token] = matches[key]
}
if values[token] != matches[key] {
return false
}
}
return true
}
// KeyMatch4Func is the wrapper for KeyMatch4.
func KeyMatch4Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyMatch4", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyMatch4(name1, name2), nil
}
// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *
// For example,
// - "/foo/bar?status=1&type=2" matches "/foo/bar"
// - "/parent/child1" and "/parent/child1" matches "/parent/*"
// - "/parent/child1?status=1" matches "/parent/*".
func KeyMatch5(key1 string, key2 string) bool {
i := strings.Index(key1, "?")
if i != -1 {
key1 = key1[:i]
}
key2 = strings.Replace(key2, "/*", "/.*", -1)
key2 = keyMatch5Re.ReplaceAllString(key2, "$1[^/]+$2")
return RegexMatch(key1, "^"+key2+"$")
}
// KeyMatch5Func is the wrapper for KeyMatch5.
func KeyMatch5Func(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "keyMatch5", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return KeyMatch5(name1, name2), nil
}
// RegexMatch determines whether key1 matches the pattern of key2 in regular expression.
func RegexMatch(key1 string, key2 string) bool {
res, err := regexp.MatchString(key2, key1)
if err != nil {
panic(err)
}
return res
}
// RegexMatchFunc is the wrapper for RegexMatch.
func RegexMatchFunc(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "regexMatch", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return RegexMatch(name1, name2), nil
}
// IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern.
// For example, "192.168.2.123" matches "192.168.2.0/24".
func IPMatch(ip1 string, ip2 string) bool {
objIP1 := net.ParseIP(ip1)
if objIP1 == nil {
panic("invalid argument: ip1 in IPMatch() function is not an IP address.")
}
_, cidr, err := net.ParseCIDR(ip2)
if err != nil {
objIP2 := net.ParseIP(ip2)
if objIP2 == nil {
panic("invalid argument: ip2 in IPMatch() function is neither an IP address nor a CIDR.")
}
return objIP1.Equal(objIP2)
}
return cidr.Contains(objIP1)
}
// IPMatchFunc is the wrapper for IPMatch.
func IPMatchFunc(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "ipMatch", err)
}
ip1 := args[0].(string)
ip2 := args[1].(string)
return IPMatch(ip1, ip2), nil
}
// GlobMatch determines whether key1 matches the pattern of key2 using glob pattern.
func GlobMatch(key1 string, key2 string) (bool, error) {
return doublestar.Match(key2, key1)
}
// GlobMatchFunc is the wrapper for GlobMatch.
func GlobMatchFunc(args ...interface{}) (interface{}, error) {
if err := validateVariadicArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "globMatch", err)
}
name1 := args[0].(string)
name2 := args[1].(string)
return GlobMatch(name1, name2)
}
// GenerateGFunction is the factory method of the g(_, _[, _]) function.
func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
memorized := sync.Map{}
return func(args ...interface{}) (interface{}, error) {
// Like all our other govaluate functions, all args are strings.
// Allocate and generate a cache key from the arguments...
total := len(args)
for _, a := range args {
aStr := a.(string)
total += len(aStr)
}
builder := strings.Builder{}
builder.Grow(total)
for _, arg := range args {
builder.WriteByte(0)
builder.WriteString(arg.(string))
}
key := builder.String()
// ...and see if we've already calculated this.
v, found := memorized.Load(key)
if found {
return v, nil
}
// If not, do the calculation.
// There are guaranteed to be exactly 2 or 3 arguments.
name1, name2 := args[0].(string), args[1].(string)
if rm == nil {
v = name1 == name2
} else if len(args) == 2 {
v, _ = rm.HasLink(name1, name2)
} else {
domain := args[2].(string)
v, _ = rm.HasLink(name1, name2, domain)
}
memorized.Store(key, v)
return v, nil
}
}
// GenerateConditionalGFunction is the factory method of the g(_, _[, _]) function with conditions.
func GenerateConditionalGFunction(crm rbac.ConditionalRoleManager) govaluate.ExpressionFunction {
return func(args ...interface{}) (interface{}, error) {
// Like all our other govaluate functions, all args are strings.
var hasLink bool
name1, name2 := args[0].(string), args[1].(string)
if crm == nil {
hasLink = name1 == name2
} else if len(args) == 2 {
hasLink, _ = crm.HasLink(name1, name2)
} else {
domain := args[2].(string)
hasLink, _ = crm.HasLink(name1, name2, domain)
}
return hasLink, nil
}
}
// builtin LinkConditionFunc
// TimeMatchFunc is the wrapper for TimeMatch.
func TimeMatchFunc(args ...string) (bool, error) {
if err := validateVariadicStringArgs(2, args...); err != nil {
return false, fmt.Errorf("%s: %w", "TimeMatch", err)
}
return TimeMatch(args[0], args[1])
}
// TimeMatch determines whether the current time is between startTime and endTime.
// You can use "_" to indicate that the parameter is ignored.
func TimeMatch(startTime, endTime string) (bool, error) {
now := time.Now()
if startTime != "_" {
if start, err := time.Parse("2006-01-02 15:04:05", startTime); err != nil {
return false, err
} else if !now.After(start) {
return false, nil
}
}
if endTime != "_" {
if end, err := time.Parse("2006-01-02 15:04:05", endTime); err != nil {
return false, err
} else if !now.Before(end) {
return false, nil
}
}
return true, nil
}
================================================
FILE: util/builtin_operators_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"testing"
)
func testKeyMatch(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes := KeyMatch(key1, key2)
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func TestKeyMatch(t *testing.T) {
testKeyMatch(t, "/foo", "/foo", true)
testKeyMatch(t, "/foo", "/foo*", true)
testKeyMatch(t, "/foo", "/foo/*", false)
testKeyMatch(t, "/foo/bar", "/foo", false)
testKeyMatch(t, "/foo/bar", "/foo*", true)
testKeyMatch(t, "/foo/bar", "/foo/*", true)
testKeyMatch(t, "/foobar", "/foo", false)
testKeyMatch(t, "/foobar", "/foo*", true)
testKeyMatch(t, "/foobar", "/foo/*", false)
}
func testKeyGet(t *testing.T, key1 string, key2 string, res string) {
t.Helper()
myRes := KeyGet(key1, key2)
t.Logf(`%s < %s: "%s"`, key1, key2, myRes)
if myRes != res {
t.Errorf(`%s < %s: "%s", supposed to be "%s"`, key1, key2, myRes, res)
}
}
func TestKeyGet(t *testing.T) {
testKeyGet(t, "/foo", "/foo", "")
testKeyGet(t, "/foo", "/foo*", "")
testKeyGet(t, "/foo", "/foo/*", "")
testKeyGet(t, "/foo/bar", "/foo", "")
testKeyGet(t, "/foo/bar", "/foo*", "/bar")
testKeyGet(t, "/foo/bar", "/foo/*", "bar")
testKeyGet(t, "/foobar", "/foo", "")
testKeyGet(t, "/foobar", "/foo*", "bar")
testKeyGet(t, "/foobar", "/foo/*", "")
}
func testKeyMatch2(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes := KeyMatch2(key1, key2)
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func testGlobMatch(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes, err := GlobMatch(key1, key2)
if err != nil {
panic(err)
}
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func TestKeyMatch2(t *testing.T) {
testKeyMatch2(t, "/foo", "/foo", true)
testKeyMatch2(t, "/foo", "/foo*", true)
testKeyMatch2(t, "/foo", "/foo/*", false)
testKeyMatch2(t, "/foo/bar", "/foo", false)
testKeyMatch2(t, "/foo/bar", "/foo*", false) // different with KeyMatch.
testKeyMatch2(t, "/foo/bar", "/foo/*", true)
testKeyMatch2(t, "/foobar", "/foo", false)
testKeyMatch2(t, "/foobar", "/foo*", false) // different with KeyMatch.
testKeyMatch2(t, "/foobar", "/foo/*", false)
testKeyMatch2(t, "/", "/:resource", false)
testKeyMatch2(t, "/resource1", "/:resource", true)
testKeyMatch2(t, "/myid", "/:id/using/:resId", false)
testKeyMatch2(t, "/myid/using/myresid", "/:id/using/:resId", true)
testKeyMatch2(t, "/proxy/myid", "/proxy/:id/*", false)
testKeyMatch2(t, "/proxy/myid/", "/proxy/:id/*", true)
testKeyMatch2(t, "/proxy/myid/res", "/proxy/:id/*", true)
testKeyMatch2(t, "/proxy/myid/res/res2", "/proxy/:id/*", true)
testKeyMatch2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/*", true)
testKeyMatch2(t, "/proxy/", "/proxy/:id/*", false)
testKeyMatch2(t, "/alice", "/:id", true)
testKeyMatch2(t, "/alice/all", "/:id/all", true)
testKeyMatch2(t, "/alice", "/:id/all", false)
testKeyMatch2(t, "/alice/all", "/:id", false)
testKeyMatch2(t, "/alice/all", "/:/all", false)
}
func testKeyGet2(t *testing.T, key1 string, key2 string, pathVar string, res string) {
t.Helper()
myRes := KeyGet2(key1, key2, pathVar)
t.Logf(`%s < %s: %s = "%s"`, key1, key2, pathVar, myRes)
if myRes != res {
t.Errorf(`%s < %s: %s = "%s" supposed to be "%s"`, key1, key2, pathVar, myRes, res)
}
}
func TestKeyGet2(t *testing.T) {
testKeyGet2(t, "/foo", "/foo", "id", "")
testKeyGet2(t, "/foo", "/foo*", "id", "")
testKeyGet2(t, "/foo", "/foo/*", "id", "")
testKeyGet2(t, "/foo/bar", "/foo", "id", "")
testKeyGet2(t, "/foo/bar", "/foo*", "id", "")
testKeyGet2(t, "/foo/bar", "/foo/*", "id", "")
testKeyGet2(t, "/foobar", "/foo", "id", "")
testKeyGet2(t, "/foobar", "/foo*", "id", "")
testKeyGet2(t, "/foobar", "/foo/*", "id", "")
testKeyGet2(t, "/", "/:resource", "resource", "")
testKeyGet2(t, "/resource1", "/:resource", "resource", "resource1")
testKeyGet2(t, "/myid", "/:id/using/:resId", "id", "")
testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "id", "myid")
testKeyGet2(t, "/myid/using/myresid", "/:id/using/:resId", "resId", "myresid")
testKeyGet2(t, "/proxy/myid", "/proxy/:id/*", "id", "")
testKeyGet2(t, "/proxy/myid/", "/proxy/:id/*", "id", "myid")
testKeyGet2(t, "/proxy/myid/res", "/proxy/:id/*", "id", "myid")
testKeyGet2(t, "/proxy/myid/res/res2", "/proxy/:id/*", "id", "myid")
testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/*", "id", "myid")
testKeyGet2(t, "/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id", "myid")
testKeyGet2(t, "/proxy/", "/proxy/:id/*", "id", "")
testKeyGet2(t, "/alice", "/:id", "id", "alice")
testKeyGet2(t, "/alice/all", "/:id/all", "id", "alice")
testKeyGet2(t, "/alice", "/:id/all", "id", "")
testKeyGet2(t, "/alice/all", "/:id", "id", "")
testKeyGet2(t, "/alice/all", "/:/all", "", "")
}
func testKeyMatch3(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes := KeyMatch3(key1, key2)
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func TestKeyMatch3(t *testing.T) {
// keyMatch3() is similar with KeyMatch2(), except using "/proxy/{id}" instead of "/proxy/:id".
testKeyMatch3(t, "/foo", "/foo", true)
testKeyMatch3(t, "/foo", "/foo*", true)
testKeyMatch3(t, "/foo", "/foo/*", false)
testKeyMatch3(t, "/foo/bar", "/foo", false)
testKeyMatch3(t, "/foo/bar", "/foo*", false)
testKeyMatch3(t, "/foo/bar", "/foo/*", true)
testKeyMatch3(t, "/foobar", "/foo", false)
testKeyMatch3(t, "/foobar", "/foo*", false)
testKeyMatch3(t, "/foobar", "/foo/*", false)
testKeyMatch3(t, "/", "/{resource}", false)
testKeyMatch3(t, "/resource1", "/{resource}", true)
testKeyMatch3(t, "/myid", "/{id}/using/{resId}", false)
testKeyMatch3(t, "/myid/using/myresid", "/{id}/using/{resId}", true)
testKeyMatch3(t, "/proxy/myid", "/proxy/{id}/*", false)
testKeyMatch3(t, "/proxy/myid/", "/proxy/{id}/*", true)
testKeyMatch3(t, "/proxy/myid/res", "/proxy/{id}/*", true)
testKeyMatch3(t, "/proxy/myid/res/res2", "/proxy/{id}/*", true)
testKeyMatch3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*", true)
testKeyMatch3(t, "/proxy/", "/proxy/{id}/*", false)
testKeyMatch3(t, "/myid/using/myresid", "/{id/using/{resId}", false)
}
func testKeyGet3(t *testing.T, key1 string, key2 string, pathVar string, res string) {
t.Helper()
myRes := KeyGet3(key1, key2, pathVar)
t.Logf(`%s < %s: %s = "%s"`, key1, key2, pathVar, myRes)
if myRes != res {
t.Errorf(`%s < %s: %s = "%s" supposed to be "%s"`, key1, key2, pathVar, myRes, res)
}
}
func TestKeyGet3(t *testing.T) {
// KeyGet3() is similar with KeyGet2(), except using "/proxy/{id}" instead of "/proxy/:id".
testKeyGet3(t, "/foo", "/foo", "id", "")
testKeyGet3(t, "/foo", "/foo*", "id", "")
testKeyGet3(t, "/foo", "/foo/*", "id", "")
testKeyGet3(t, "/foo/bar", "/foo", "id", "")
testKeyGet3(t, "/foo/bar", "/foo*", "id", "")
testKeyGet3(t, "/foo/bar", "/foo/*", "id", "")
testKeyGet3(t, "/foobar", "/foo", "id", "")
testKeyGet3(t, "/foobar", "/foo*", "id", "")
testKeyGet3(t, "/foobar", "/foo/*", "id", "")
testKeyGet3(t, "/", "/{resource}", "resource", "")
testKeyGet3(t, "/resource1", "/{resource}", "resource", "resource1")
testKeyGet3(t, "/myid", "/{id}/using/{resId}", "id", "")
testKeyGet3(t, "/myid/using/myresid", "/{id}/using/{resId}", "id", "myid")
testKeyGet3(t, "/myid/using/myresid", "/{id}/using/{resId}", "resId", "myresid")
testKeyGet3(t, "/proxy/myid", "/proxy/{id}/*", "id", "")
testKeyGet3(t, "/proxy/myid/", "/proxy/{id}/*", "id", "myid")
testKeyGet3(t, "/proxy/myid/res", "/proxy/{id}/*", "id", "myid")
testKeyGet3(t, "/proxy/myid/res/res2", "/proxy/{id}/*", "id", "myid")
testKeyGet3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id", "myid")
testKeyGet3(t, "/proxy/", "/proxy/{id}/*", "id", "")
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{proj}_admin/info",
"proj", "")
testKeyGet3(t, "/{id/using/myresid", "/{id/using/{resId}", "resId", "myresid")
testKeyGet3(t, "/{id/using/myresid/status}", "/{id/using/{resId}/status}", "resId", "myresid")
testKeyGet3(t, "/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res", "res3")
testKeyGet3(t, "/api/project1_admin/info", "/api/{proj}_admin/info", "proj", "project1")
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
"g", "group1")
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
"gn", "group_name")
testKeyGet3(t, "/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info",
"proj", "project1")
}
func testKeyMatch4(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes := KeyMatch4(key1, key2)
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func TestKeyMatch4(t *testing.T) {
testKeyMatch4(t, "/parent/123/child/123", "/parent/{id}/child/{id}", true)
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{id}", false)
testKeyMatch4(t, "/parent/123/child/123", "/parent/{id}/child/{another_id}", true)
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{another_id}", true)
testKeyMatch4(t, "/parent/123/child/123/book/123", "/parent/{id}/child/{id}/book/{id}", true)
testKeyMatch4(t, "/parent/123/child/123/book/456", "/parent/{id}/child/{id}/book/{id}", false)
testKeyMatch4(t, "/parent/123/child/456/book/123", "/parent/{id}/child/{id}/book/{id}", false)
testKeyMatch4(t, "/parent/123/child/456/book/", "/parent/{id}/child/{id}/book/{id}", false)
testKeyMatch4(t, "/parent/123/child/456", "/parent/{id}/child/{id}/book/{id}", false)
testKeyMatch4(t, "/parent/123/child/123", "/parent/{i/d}/child/{i/d}", false)
}
func testRegexMatch(t *testing.T, key1 string, key2 string, res bool) {
t.Helper()
myRes := RegexMatch(key1, key2)
t.Logf("%s < %s: %t", key1, key2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", key1, key2, !res, res)
}
}
func TestRegexMatch(t *testing.T) {
testRegexMatch(t, "/topic/create", "/topic/create", true)
testRegexMatch(t, "/topic/create/123", "/topic/create", true)
testRegexMatch(t, "/topic/delete", "/topic/create", false)
testRegexMatch(t, "/topic/edit", "/topic/edit/[0-9]+", false)
testRegexMatch(t, "/topic/edit/123", "/topic/edit/[0-9]+", true)
testRegexMatch(t, "/topic/edit/abc", "/topic/edit/[0-9]+", false)
testRegexMatch(t, "/foo/delete/123", "/topic/delete/[0-9]+", false)
testRegexMatch(t, "/topic/delete/0", "/topic/delete/[0-9]+", true)
testRegexMatch(t, "/topic/edit/123s", "/topic/delete/[0-9]+", false)
}
func testIPMatch(t *testing.T, ip1 string, ip2 string, res bool) {
t.Helper()
myRes := IPMatch(ip1, ip2)
t.Logf("%s < %s: %t", ip1, ip2, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", ip1, ip2, !res, res)
}
}
func TestIPMatch(t *testing.T) {
testIPMatch(t, "192.168.2.123", "192.168.2.0/24", true)
testIPMatch(t, "192.168.2.123", "192.168.3.0/24", false)
testIPMatch(t, "192.168.2.123", "192.168.2.0/16", true)
testIPMatch(t, "192.168.2.123", "192.168.2.123", true)
testIPMatch(t, "192.168.2.123", "192.168.2.123/32", true)
testIPMatch(t, "10.0.0.11", "10.0.0.0/8", true)
testIPMatch(t, "11.0.0.123", "10.0.0.0/8", false)
}
func testRegexMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := RegexMatchFunc(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testKeyMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := KeyMatchFunc(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testKeyMatch2Func(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := KeyMatch2Func(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testKeyMatch3Func(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := KeyMatch3Func(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testKeyMatch4Func(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := KeyMatch4Func(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testKeyMatch5Func(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := KeyMatch5Func(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func testIPMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {
t.Helper()
myRes, myErr := IPMatchFunc(args...)
myErrStr := ""
if myErr != nil {
myErrStr = myErr.Error()
}
if myRes != res || err != myErrStr {
t.Errorf("%v returns %v %v, supposed to be %v %v", args, myRes, myErr, res, err)
}
}
func TestRegexMatchFunc(t *testing.T) {
testRegexMatchFunc(t, false, "regexMatch: expected 2 arguments, but got 1", "/topic/create")
testRegexMatchFunc(t, false, "regexMatch: expected 2 arguments, but got 3", "/topic/create/123", "/topic/create", "/topic/update")
testRegexMatchFunc(t, false, "regexMatch: argument must be a string", "/topic/create", false)
testRegexMatchFunc(t, true, "", "/topic/create/123", "/topic/create")
}
func TestKeyMatchFunc(t *testing.T) {
testKeyMatchFunc(t, false, "keyMatch: expected 2 arguments, but got 1", "/foo")
testKeyMatchFunc(t, false, "keyMatch: expected 2 arguments, but got 3", "/foo/create/123", "/foo/*", "/foo/update/123")
testKeyMatchFunc(t, false, "keyMatch: argument must be a string", "/foo", true)
testKeyMatchFunc(t, false, "", "/foo/bar", "/foo")
testKeyMatchFunc(t, true, "", "/foo/bar", "/foo/*")
testKeyMatchFunc(t, true, "", "/foo/bar", "/foo*")
}
func TestKeyMatch2Func(t *testing.T) {
testKeyMatch2Func(t, false, "keyMatch2: expected 2 arguments, but got 1", "/")
testKeyMatch2Func(t, false, "keyMatch2: expected 2 arguments, but got 3", "/foo/create/123", "/*", "/foo/update/123")
testKeyMatch2Func(t, false, "keyMatch2: argument must be a string", "/foo", true)
testKeyMatch2Func(t, false, "", "/", "/:resource")
testKeyMatch2Func(t, true, "", "/resource1", "/:resource")
testKeyMatch2Func(t, true, "", "/foo", "/foo")
testKeyMatch2Func(t, true, "", "/foo", "/foo*")
testKeyMatch2Func(t, false, "", "/foo", "/foo/*")
}
func TestKeyMatch3Func(t *testing.T) {
testKeyMatch3Func(t, false, "keyMatch3: expected 2 arguments, but got 1", "/")
testKeyMatch3Func(t, false, "keyMatch3: expected 2 arguments, but got 3", "/foo/create/123", "/*", "/foo/update/123")
testKeyMatch3Func(t, false, "keyMatch3: argument must be a string", "/foo", true)
testKeyMatch3Func(t, true, "", "/foo", "/foo")
testKeyMatch3Func(t, true, "", "/foo", "/foo*")
testKeyMatch3Func(t, false, "", "/foo", "/foo/*")
testKeyMatch3Func(t, false, "", "/foo/bar", "/foo")
testKeyMatch3Func(t, false, "", "/foo/bar", "/foo*")
testKeyMatch3Func(t, true, "", "/foo/bar", "/foo/*")
testKeyMatch3Func(t, false, "", "/foobar", "/foo")
testKeyMatch3Func(t, false, "", "/foobar", "/foo*")
testKeyMatch3Func(t, false, "", "/foobar", "/foo/*")
testKeyMatch3Func(t, false, "", "/", "/{resource}")
testKeyMatch3Func(t, true, "", "/resource1", "/{resource}")
testKeyMatch3Func(t, false, "", "/myid", "/{id}/using/{resId}")
testKeyMatch3Func(t, true, "", "/myid/using/myresid", "/{id}/using/{resId}")
testKeyMatch3Func(t, false, "", "/proxy/myid", "/proxy/{id}/*")
testKeyMatch3Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
testKeyMatch3Func(t, true, "", "/proxy/myid/res", "/proxy/{id}/*")
testKeyMatch3Func(t, true, "", "/proxy/myid/res/res2", "/proxy/{id}/*")
testKeyMatch3Func(t, true, "", "/proxy/myid/res/res2/res3", "/proxy/{id}/*")
testKeyMatch3Func(t, false, "", "/proxy/", "/proxy/{id}/*")
}
func TestKeyMatch4Func(t *testing.T) {
testKeyMatch4Func(t, false, "keyMatch4: expected 2 arguments, but got 1", "/parent/123/child/123")
testKeyMatch4Func(t, false, "keyMatch4: expected 2 arguments, but got 3", "/parent/123/child/123", "/parent/{id}/child/{id}", true)
testKeyMatch4Func(t, false, "keyMatch4: argument must be a string", "/parent/123/child/123", true)
testKeyMatch4Func(t, true, "", "/parent/123/child/123", "/parent/{id}/child/{id}")
testKeyMatch4Func(t, false, "", "/parent/123/child/456", "/parent/{id}/child/{id}")
testKeyMatch4Func(t, true, "", "/parent/123/child/123", "/parent/{id}/child/{another_id}")
testKeyMatch4Func(t, true, "", "/parent/123/child/456", "/parent/{id}/child/{another_id}")
}
func TestKeyMatch5Func(t *testing.T) {
testKeyMatch5Func(t, false, "keyMatch5: expected 2 arguments, but got 1", "/foo")
testKeyMatch5Func(t, false, "keyMatch5: expected 2 arguments, but got 3", "/foo/create/123", "/foo/*", "/foo/update/123")
testKeyMatch5Func(t, false, "keyMatch5: argument must be a string", "/parent/123", true)
testKeyMatch5Func(t, true, "", "/parent/child?status=1&type=2", "/parent/child")
testKeyMatch5Func(t, false, "", "/parent?status=1&type=2", "/parent/child")
testKeyMatch5Func(t, true, "", "/parent/child/?status=1&type=2", "/parent/child/")
testKeyMatch5Func(t, false, "", "/parent/child/?status=1&type=2", "/parent/child")
testKeyMatch5Func(t, false, "", "/parent/child?status=1&type=2", "/parent/child/")
testKeyMatch5Func(t, true, "", "/foo", "/foo")
testKeyMatch5Func(t, true, "", "/foo", "/foo*")
testKeyMatch5Func(t, false, "", "/foo", "/foo/*")
testKeyMatch5Func(t, false, "", "/foo/bar", "/foo")
testKeyMatch5Func(t, false, "", "/foo/bar", "/foo*")
testKeyMatch5Func(t, true, "", "/foo/bar", "/foo/*")
testKeyMatch5Func(t, false, "", "/foobar", "/foo")
testKeyMatch5Func(t, false, "", "/foobar", "/foo*")
testKeyMatch5Func(t, false, "", "/foobar", "/foo/*")
testKeyMatch5Func(t, false, "", "/", "/{resource}")
testKeyMatch5Func(t, true, "", "/resource1", "/{resource}")
testKeyMatch5Func(t, false, "", "/myid", "/{id}/using/{resId}")
testKeyMatch5Func(t, true, "", "/myid/using/myresid", "/{id}/using/{resId}")
testKeyMatch5Func(t, false, "", "/proxy/myid", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2/res3", "/proxy/{id}/*")
testKeyMatch5Func(t, false, "", "/proxy/", "/proxy/{id}/*")
testKeyMatch5Func(t, false, "", "/proxy/myid?status=1&type=2", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res?status=1&type=2", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2?status=1&type=2", "/proxy/{id}/*")
testKeyMatch5Func(t, true, "", "/proxy/myid/res/res2/res3?status=1&type=2", "/proxy/{id}/*")
testKeyMatch5Func(t, false, "", "/proxy/", "/proxy/{id}/*")
}
func TestIPMatchFunc(t *testing.T) {
testIPMatchFunc(t, false, "ipMatch: expected 2 arguments, but got 1", "192.168.2.123")
testIPMatchFunc(t, false, "ipMatch: argument must be a string", "192.168.2.123", 128)
testIPMatchFunc(t, true, "", "192.168.2.123", "192.168.2.0/24")
}
func TestGlobMatch(t *testing.T) {
testGlobMatch(t, "/foo", "/foo", true)
testGlobMatch(t, "/foo", "/foo*", true)
testGlobMatch(t, "/foo", "/foo/*", false)
testGlobMatch(t, "/foo/bar", "/foo", false)
testGlobMatch(t, "/foo/bar", "/foo*", false)
testGlobMatch(t, "/foo/bar", "/foo/*", true)
testGlobMatch(t, "/foobar", "/foo", false)
testGlobMatch(t, "/foobar", "/foo*", true)
testGlobMatch(t, "/foobar", "/foo/*", false)
testGlobMatch(t, "/foo", "*/foo", true)
testGlobMatch(t, "/foo", "*/foo*", true)
testGlobMatch(t, "/foo", "*/foo/*", false)
testGlobMatch(t, "/foo/bar", "*/foo", false)
testGlobMatch(t, "/foo/bar", "*/foo*", false)
testGlobMatch(t, "/foo/bar", "*/foo/*", true)
testGlobMatch(t, "/foobar", "*/foo", false)
testGlobMatch(t, "/foobar", "*/foo*", true)
testGlobMatch(t, "/foobar", "*/foo/*", false)
testGlobMatch(t, "/prefix/foo", "*/foo", false)
testGlobMatch(t, "/prefix/foo", "*/foo*", false)
testGlobMatch(t, "/prefix/foo", "*/foo/*", false)
testGlobMatch(t, "/prefix/foo/bar", "*/foo", false)
testGlobMatch(t, "/prefix/foo/bar", "*/foo*", false)
testGlobMatch(t, "/prefix/foo/bar", "*/foo/*", false)
testGlobMatch(t, "/prefix/foobar", "*/foo", false)
testGlobMatch(t, "/prefix/foobar", "*/foo*", false)
testGlobMatch(t, "/prefix/foobar", "*/foo/*", false)
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo", false)
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo*", false)
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo/*", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo*", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo/*", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo*", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/*", false)
testGlobMatch(t, "/foo", "**/foo", true)
testGlobMatch(t, "/foo", "**/foo**", true)
testGlobMatch(t, "/foo", "**/foo/**", true)
testGlobMatch(t, "/foo/bar", "**/foo", false)
testGlobMatch(t, "/foo/bar", "**/foo**", false)
testGlobMatch(t, "/foo/bar", "**/foo/**", true)
testGlobMatch(t, "/foobar", "**/foo", false)
testGlobMatch(t, "/foobar", "**/foo**", true)
testGlobMatch(t, "/foobar", "**/foo/**", false)
testGlobMatch(t, "/prefix/foo", "**/foo", true)
testGlobMatch(t, "/prefix/foo", "**/foo**", true)
testGlobMatch(t, "/prefix/foo", "**/foo/**", true)
testGlobMatch(t, "/prefix/foo/bar", "**/foo", false)
testGlobMatch(t, "/prefix/foo/bar", "**/foo**", false)
testGlobMatch(t, "/prefix/foo/bar", "**/foo/**", true)
testGlobMatch(t, "/prefix/foobar", "**/foo", false)
testGlobMatch(t, "/prefix/foobar", "**/foo**", true)
testGlobMatch(t, "/prefix/foobar", "**/foo/**", false)
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo", true)
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo**", true)
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/**", true)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo**", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/**", true)
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo**", true)
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/**", false)
testGlobMatch(t, "/foo", "*/foo**", true)
testGlobMatch(t, "/foo", "**/foo*", true)
testGlobMatch(t, "/foo", "*/foo/**", true)
testGlobMatch(t, "/foo", "**/foo/*", false)
testGlobMatch(t, "/foo/bar", "*/foo**", false)
testGlobMatch(t, "/foo/bar", "**/foo*", false)
testGlobMatch(t, "/foo/bar", "*/foo/**", true)
testGlobMatch(t, "/foo/bar", "**/foo/*", true)
testGlobMatch(t, "/foobar", "*/foo**", true)
testGlobMatch(t, "/foobar", "**/foo*", true)
testGlobMatch(t, "/foobar", "*/foo/**", false)
testGlobMatch(t, "/foobar", "**/foo/*", false)
testGlobMatch(t, "/prefix/foo", "*/foo**", false)
testGlobMatch(t, "/prefix/foo", "**/foo*", true)
testGlobMatch(t, "/prefix/foo", "*/foo/**", false)
testGlobMatch(t, "/prefix/foo", "**/foo/*", false)
testGlobMatch(t, "/prefix/foo/bar", "*/foo**", false)
testGlobMatch(t, "/prefix/foo/bar", "**/foo*", false)
testGlobMatch(t, "/prefix/foo/bar", "*/foo/**", false)
testGlobMatch(t, "/prefix/foo/bar", "**/foo/*", true)
testGlobMatch(t, "/prefix/foobar", "*/foo**", false)
testGlobMatch(t, "/prefix/foobar", "**/foo*", true)
testGlobMatch(t, "/prefix/foobar", "*/foo/**", false)
testGlobMatch(t, "/prefix/foobar", "**/foo/*", false)
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo**", false)
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo*", true)
testGlobMatch(t, "/prefix/subprefix/foo", "*/foo/**", false)
testGlobMatch(t, "/prefix/subprefix/foo", "**/foo/*", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo**", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo*", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "*/foo/**", false)
testGlobMatch(t, "/prefix/subprefix/foo/bar", "**/foo/*", true)
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo**", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo*", true)
testGlobMatch(t, "/prefix/subprefix/foobar", "*/foo/**", false)
testGlobMatch(t, "/prefix/subprefix/foobar", "**/foo/*", false)
}
func testTimeMatch(t *testing.T, startTime string, endTime string, res bool) {
t.Helper()
myRes, err := TimeMatch(startTime, endTime)
if err != nil {
panic(err)
}
t.Logf("%s < %s: %t", startTime, endTime, myRes)
if myRes != res {
t.Errorf("%s < %s: %t, supposed to be %t", startTime, endTime, !res, res)
}
}
func TestTestMatch(t *testing.T) {
testTimeMatch(t, "0000-01-01 00:00:00", "0000-01-02 00:00:00", false)
testTimeMatch(t, "0000-01-01 00:00:00", "9999-12-30 00:00:00", true)
testTimeMatch(t, "_", "_", true)
testTimeMatch(t, "_", "9999-12-30 00:00:00", true)
testTimeMatch(t, "_", "0000-01-02 00:00:00", false)
testTimeMatch(t, "0000-01-01 00:00:00", "_", true)
testTimeMatch(t, "9999-12-30 00:00:00", "_", false)
}
================================================
FILE: util/util.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"encoding/json"
"regexp"
"sort"
"strings"
"sync"
)
var evalReg = regexp.MustCompile(`\beval\((?P[^)]*)\)`)
var escapeAssertionRegex = regexp.MustCompile(`([()\s|&,=!><+\-*/]|^)((r|p)[0-9]*)\.`)
func JsonToMap(jsonStr string) (map[string]interface{}, error) {
result := make(map[string]interface{})
err := json.Unmarshal([]byte(jsonStr), &result)
if err != nil {
return result, err
}
return result, nil
}
// EscapeAssertion escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.
func EscapeAssertion(s string) string {
s = escapeAssertionRegex.ReplaceAllStringFunc(s, func(m string) string {
// Replace only the last dot with underscore (preserve the prefix character)
lastDotIdx := strings.LastIndex(m, ".")
if lastDotIdx > 0 {
return m[:lastDotIdx] + "_"
}
return m
})
return s
}
// RemoveComments removes the comments starting with # in the text.
func RemoveComments(s string) string {
pos := strings.Index(s, "#")
if pos == -1 {
return s
}
return strings.TrimSpace(s[0:pos])
}
// ArrayEquals determines whether two string arrays are identical.
func ArrayEquals(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// Array2DEquals determines whether two 2-dimensional string arrays are identical.
func Array2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if !ArrayEquals(v, b[i]) {
return false
}
}
return true
}
// SortArray2D Sorts the two-dimensional string array.
func SortArray2D(arr [][]string) {
if len(arr) == 0 {
return
}
sort.Slice(arr, func(i, j int) bool {
minArrLen := len(arr[i])
if len(arr[j]) < minArrLen {
minArrLen = len(arr[j])
}
for k := 0; k < minArrLen; k++ {
if arr[i][k] != arr[j][k] {
return arr[i][k] < arr[j][k]
}
}
return len(arr[i]) < len(arr[j])
})
}
// SortedArray2DEquals determines whether two 2-dimensional string arrays are identical.
func SortedArray2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
copyA := make([][]string, len(a))
copy(copyA, a)
copyB := make([][]string, len(b))
copy(copyB, b)
SortArray2D(copyA)
SortArray2D(copyB)
for i, v := range copyA {
if !ArrayEquals(v, copyB[i]) {
return false
}
}
return true
}
// ArrayRemoveDuplicates removes any duplicated elements in a string array.
func ArrayRemoveDuplicates(s *[]string) {
found := make(map[string]bool)
j := 0
for i, x := range *s {
if !found[x] {
found[x] = true
(*s)[j] = (*s)[i]
j++
}
}
*s = (*s)[:j]
}
// ArrayToString gets a printable string for a string array.
func ArrayToString(s []string) string {
return strings.Join(s, ", ")
}
// ParamsToString gets a printable string for variable number of parameters.
func ParamsToString(s ...string) string {
return strings.Join(s, ", ")
}
// SetEquals determines whether two string sets are identical.
func SetEquals(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
sort.Strings(a)
sort.Strings(b)
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// SetEquals determines whether two int sets are identical.
func SetEqualsInt(a []int, b []int) bool {
if len(a) != len(b) {
return false
}
sort.Ints(a)
sort.Ints(b)
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// Set2DEquals determines whether two string slice sets are identical.
func Set2DEquals(a [][]string, b [][]string) bool {
if len(a) != len(b) {
return false
}
var aa []string
for _, v := range a {
sort.Strings(v)
aa = append(aa, strings.Join(v, ", "))
}
var bb []string
for _, v := range b {
sort.Strings(v)
bb = append(bb, strings.Join(v, ", "))
}
return SetEquals(aa, bb)
}
// JoinSlice joins a string and a slice into a new slice.
func JoinSlice(a string, b ...string) []string {
res := make([]string, 0, len(b)+1)
res = append(res, a)
res = append(res, b...)
return res
}
// JoinSliceAny joins a string and a slice into a new interface{} slice.
func JoinSliceAny(a string, b ...string) []interface{} {
res := make([]interface{}, 0, len(b)+1)
res = append(res, a)
for _, s := range b {
res = append(res, s)
}
return res
}
// SetSubtract returns the elements in `a` that aren't in `b`.
func SetSubtract(a []string, b []string) []string {
mb := make(map[string]struct{}, len(b))
for _, x := range b {
mb[x] = struct{}{}
}
var diff []string
for _, x := range a {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
// HasEval determine whether matcher contains function eval.
func HasEval(s string) bool {
return evalReg.MatchString(s)
}
// ReplaceEval replace function eval with the value of its parameters.
func ReplaceEval(s string, rule string) string {
return evalReg.ReplaceAllString(s, "("+rule+")")
}
// ReplaceEvalWithMap replace function eval with the value of its parameters via given sets.
func ReplaceEvalWithMap(src string, sets map[string]string) string {
return evalReg.ReplaceAllStringFunc(src, func(s string) string {
subs := evalReg.FindStringSubmatch(s)
if subs == nil {
return s
}
key := subs[1]
value, found := sets[key]
if !found {
return s
}
return evalReg.ReplaceAllString(s, value)
})
}
// GetEvalValue returns the parameters of function eval.
func GetEvalValue(s string) []string {
subMatch := evalReg.FindAllStringSubmatch(s, -1)
var rules []string
for _, rule := range subMatch {
rules = append(rules, rule[1])
}
return rules
}
// EscapeStringLiterals escapes backslashes in string literals within an expression
// to ensure consistent handling between govaluate (which interprets escape sequences)
// and CSV parsing (which treats backslashes as literal characters).
// This function doubles all backslashes within single-quoted and double-quoted strings.
func EscapeStringLiterals(expr string) string {
var result strings.Builder
inString := false
var quote rune
for i := 0; i < len(expr); i++ {
ch := rune(expr[i])
if inString {
result.WriteRune(ch)
if ch == '\\' {
// Found a backslash inside a string - double it
result.WriteRune('\\')
} else if ch == quote {
// End of string literal
inString = false
}
continue
}
// Not inside a string literal
if ch == '\'' || ch == '"' {
inString = true
quote = ch
}
result.WriteRune(ch)
}
return result.String()
}
func RemoveDuplicateElement(s []string) []string {
result := make([]string, 0, len(s))
temp := map[string]struct{}{}
for _, item := range s {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
type node struct {
key interface{}
value interface{}
prev *node
next *node
}
type LRUCache struct {
capacity int
m map[interface{}]*node
head *node
tail *node
}
func NewLRUCache(capacity int) *LRUCache {
cache := &LRUCache{}
cache.capacity = capacity
cache.m = map[interface{}]*node{}
head := &node{}
tail := &node{}
head.next = tail
tail.prev = head
cache.head = head
cache.tail = tail
return cache
}
func (cache *LRUCache) remove(n *node, listOnly bool) {
if !listOnly {
delete(cache.m, n.key)
}
n.prev.next = n.next
n.next.prev = n.prev
}
func (cache *LRUCache) add(n *node, listOnly bool) {
if !listOnly {
cache.m[n.key] = n
}
headNext := cache.head.next
cache.head.next = n
headNext.prev = n
n.next = headNext
n.prev = cache.head
}
func (cache *LRUCache) moveToHead(n *node) {
cache.remove(n, true)
cache.add(n, true)
}
func (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) {
n, ok := cache.m[key]
if ok {
cache.moveToHead(n)
return n.value, ok
} else {
return nil, ok
}
}
func (cache *LRUCache) Put(key interface{}, value interface{}) {
n, ok := cache.m[key]
if ok {
cache.remove(n, false)
} else {
n = &node{key, value, nil, nil}
if len(cache.m) >= cache.capacity {
cache.remove(cache.tail.prev, false)
}
}
cache.add(n, false)
}
type SyncLRUCache struct {
rwm sync.RWMutex
*LRUCache
}
func NewSyncLRUCache(capacity int) *SyncLRUCache {
cache := &SyncLRUCache{}
cache.LRUCache = NewLRUCache(capacity)
return cache
}
func (cache *SyncLRUCache) Get(key interface{}) (value interface{}, ok bool) {
cache.rwm.Lock()
defer cache.rwm.Unlock()
return cache.LRUCache.Get(key)
}
func (cache *SyncLRUCache) Put(key interface{}, value interface{}) {
cache.rwm.Lock()
defer cache.rwm.Unlock()
cache.LRUCache.Put(key, value)
}
================================================
FILE: util/util_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"testing"
)
func testEscapeAssertion(t *testing.T, s string, res string) {
t.Helper()
myRes := EscapeAssertion(s)
t.Logf("%s: %s", s, myRes)
if myRes != res {
t.Errorf("%s: %s, supposed to be %s", s, myRes, res)
}
}
func TestEscapeAssertion(t *testing.T) {
testEscapeAssertion(t, "r_sub == r_obj.value", "r_sub == r_obj.value")
testEscapeAssertion(t, "p_sub == r_sub.value", "p_sub == r_sub.value")
testEscapeAssertion(t, "r.attr.value == p.attr", "r_attr.value == p_attr")
testEscapeAssertion(t, "r.attr.value == p.attr", "r_attr.value == p_attr")
testEscapeAssertion(t, "r.attp.value || p.attr", "r_attp.value || p_attr")
testEscapeAssertion(t, "r2.attr.value == p2.attr", "r2_attr.value == p2_attr")
testEscapeAssertion(t, "r2.attp.value || p2.attr", "r2_attp.value || p2_attr")
testEscapeAssertion(t, "r.attp.value &&p.attr", "r_attp.value &&p_attr")
testEscapeAssertion(t, "r.attp.value >p.attr", "r_attp.value >p_attr")
testEscapeAssertion(t, "r.attp.value 0 {
if s, isString := rvals[0].(string); isString {
entry.Subject = s
}
}
if len(rvals) > 1 {
if o, isString := rvals[1].(string); isString {
entry.Object = o
}
}
if len(rvals) > 2 {
if a, isString := rvals[2].(string); isString {
entry.Action = a
}
}
if len(rvals) > 3 {
if d, isString := rvals[3].(string); isString {
entry.Domain = d
}
}
return entry
}
// onLogBeforeEventInEnforce initializes logging for Enforce operation.
func (e *Enforcer) onLogBeforeEventInEnforce(rvals []interface{}) *log.LogEntry {
if e.logger == nil {
return nil
}
logEntry := e.createEnforceLogEntry(rvals)
_ = e.logger.OnBeforeEvent(logEntry)
return logEntry
}
// onLogAfterEventInEnforce finalizes logging for Enforce operation.
func (e *Enforcer) onLogAfterEventInEnforce(logEntry *log.LogEntry, allowed bool) {
if e.logger != nil && logEntry != nil {
logEntry.Allowed = allowed
_ = e.logger.OnAfterEvent(logEntry)
}
}
// logPolicyOperation logs a policy operation (add or remove) with before and after events.
func (e *Enforcer) logPolicyOperation(eventType log.EventType, sec string, rule []string, operation func() (bool, error)) (bool, error) {
var logEntry *log.LogEntry
if e.logger != nil && sec == "p" {
logEntry = &log.LogEntry{
EventType: eventType,
Rules: [][]string{rule},
}
_ = e.logger.OnBeforeEvent(logEntry)
}
ok, err := operation()
if e.logger != nil && logEntry != nil {
if ok && err == nil {
logEntry.RuleCount = 1
} else {
logEntry.RuleCount = 0
logEntry.Error = err
}
_ = e.logger.OnAfterEvent(logEntry)
}
return ok, err
}
================================================
FILE: watcher_ex_test.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
"github.com/casbin/casbin/v3/model"
)
type SampleWatcherEx struct {
SampleWatcher
}
func (w SampleWatcherEx) UpdateForAddPolicy(sec, ptype string, params ...string) error {
return nil
}
func (w SampleWatcherEx) UpdateForRemovePolicy(sec, ptype string, params ...string) error {
return nil
}
func (w SampleWatcherEx) UpdateForRemoveFilteredPolicy(sec, ptype string, fieldIndex int, fieldValues ...string) error {
return nil
}
func (w SampleWatcherEx) UpdateForSavePolicy(model model.Model) error {
return nil
}
func (w SampleWatcherEx) UpdateForAddPolicies(sec string, ptype string, rules ...[]string) error {
return nil
}
func (w SampleWatcherEx) UpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error {
return nil
}
func TestSetWatcherEx(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
sampleWatcherEx := &SampleWatcherEx{}
err := e.SetWatcher(sampleWatcherEx)
if err != nil {
t.Fatal(err)
}
_ = e.SavePolicy() // calls watcherEx.UpdateForSavePolicy()
_, _ = e.AddPolicy("admin", "data1", "read") // calls watcherEx.UpdateForAddPolicy()
_, _ = e.RemovePolicy("admin", "data1", "read") // calls watcherEx.UpdateForRemovePolicy()
_, _ = e.RemoveFilteredPolicy(1, "data1") // calls watcherEx.UpdateForRemoveFilteredPolicy()
_, _ = e.RemovePolicy("admin", "data1", "read") // calls watcherEx.UpdateForRemovePolicy()
_, _ = e.AddGroupingPolicy("g:admin", "data1")
_, _ = e.RemoveGroupingPolicy("g:admin", "data1")
_, _ = e.AddGroupingPolicy("g:admin", "data1")
_, _ = e.RemoveFilteredGroupingPolicy(1, "data1")
_, _ = e.AddPolicies([][]string{{"admin", "data1", "read"}, {"admin", "data2", "read"}}) // calls watcherEx.UpdateForAddPolicies()
_, _ = e.RemovePolicies([][]string{{"admin", "data1", "read"}, {"admin", "data2", "read"}}) // calls watcherEx.UpdateForRemovePolicies()
}
================================================
FILE: watcher_test.go
================================================
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import "testing"
type SampleWatcher struct {
callback func(string)
}
func (w *SampleWatcher) Close() {
}
func (w *SampleWatcher) SetUpdateCallback(callback func(string)) error {
w.callback = callback
return nil
}
func (w *SampleWatcher) Update() error {
if w.callback != nil {
w.callback("")
}
return nil
}
func TestSetWatcher(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatal(err)
}
sampleWatcher := &SampleWatcher{}
err = e.SetWatcher(sampleWatcher)
if err != nil {
t.Fatal(err)
}
err = e.SavePolicy() // calls watcher.Update()
if err != nil {
t.Fatal(err)
}
}
func TestSelfModify(t *testing.T) {
e, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
if err != nil {
t.Fatal(err)
}
sampleWatcher := &SampleWatcher{}
err = e.SetWatcher(sampleWatcher)
if err != nil {
t.Fatal(err)
}
var called int
called = -1
_ = e.watcher.SetUpdateCallback(func(s string) {
called = 1
})
_, err = e.AddPolicy("eva", "data", "read") // calls watcher.Update()
if err != nil {
t.Fatal(err)
}
if called != 1 {
t.Fatal("callback should be called")
}
called = -1
_ = e.watcher.SetUpdateCallback(func(s string) {
called = 1
})
_, err = e.SelfAddPolicy("p", "p", []string{"eva", "data", "write"}) // calls watcher.Update()
if err != nil {
t.Fatal(err)
}
if called != -1 {
t.Fatal("callback should not be called")
}
}
================================================
FILE: watcher_update_test.go
================================================
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casbin
import (
"testing"
)
type SampleWatcherUpdatable struct {
SampleWatcher
}
func (w SampleWatcherUpdatable) UpdateForUpdatePolicy(params ...string) error {
return nil
}
func TestSetWatcherUpdatable(t *testing.T) {
e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
sampleWatcherEx := &SampleWatcherUpdatable{}
err := e.SetWatcher(sampleWatcherEx)
if err != nil {
t.Fatal(err)
}
_ = e.SavePolicy() // calls watcherEx.UpdateForSavePolicy()
_, _ = e.UpdatePolicy([]string{"admin", "data1", "read"}, []string{"admin", "data2", "read"}) // calls watcherEx.UpdateForUpdatePolicy()
}