snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
29
.asf.yaml
Normal file
29
.asf.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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
|
||||
|
||||
21
.gitattributes
vendored
Normal file
21
.gitattributes
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
# Repository-only development infrastructure and contributor guidance are not
|
||||
# part of the source release.
|
||||
.github export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
248
.github/scripts/benchmark_formatter.py
vendored
Normal file
248
.github/scripts/benchmark_formatter.py
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
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)
|
||||
32
.github/scripts/download_artifact.js
vendored
Normal file
32
.github/scripts/download_artifact.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
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}`);
|
||||
}
|
||||
};
|
||||
59
.github/scripts/post_comment.js
vendored
Normal file
59
.github/scripts/post_comment.js
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
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 = '<sub>🤖 This comment will be automatically updated with the latest benchmark results.</sub>';
|
||||
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
|
||||
});
|
||||
}
|
||||
};
|
||||
2
.github/semantic.yml
vendored
Normal file
2
.github/semantic.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Always validate the PR title AND all the commits
|
||||
titleAndCommits: true
|
||||
37
.github/workflows/comment.yml
vendored
Normal file
37
.github/workflows/comment.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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})
|
||||
44
.github/workflows/default.yml
vendored
Normal file
44
.github/workflows/default.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == '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
|
||||
26
.github/workflows/golangci-lint.yml
vendored
Normal file
26
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: golangci-lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == '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@v8
|
||||
97
.github/workflows/performance-pr.yml
vendored
Normal file
97
.github/workflows/performance-pr.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: Performance Comparison for Pull Requests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
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
|
||||
204
.github/workflows/release.yml
vendored
Normal file
204
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
# 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.
|
||||
|
||||
# Releases are driven by manually pushed tags only. Nothing is published
|
||||
# automatically when a pull request is merged.
|
||||
#
|
||||
# v<X>.<Y>.<Z>-rc<N> GitHub pre-release only.
|
||||
# v<X>.<Y>.<Z> GitHub release plus the Go module proxy.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ github.repository == 'apache/casbin' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compute release metadata
|
||||
id: release
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TAG_NAME="${GITHUB_REF_NAME}"
|
||||
if [[ "${TAG_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||
IS_RC="false"
|
||||
elif [[ "${TAG_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-rc[0-9]+$ ]]; then
|
||||
IS_RC="true"
|
||||
else
|
||||
echo "::error::Unsupported release tag: ${TAG_NAME}. Expected vX.Y.Z or vX.Y.Z-rcN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${BASH_REMATCH[1]}"
|
||||
BASENAME="apache-casbin-incubating-${VERSION}-src"
|
||||
|
||||
# Previous release, used as the start of the change log range.
|
||||
PREVIOUS_TAG="$(
|
||||
{
|
||||
git tag --list 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true
|
||||
echo "v${VERSION}"
|
||||
} |
|
||||
sort -V -u |
|
||||
awk -v current="v${VERSION}" '
|
||||
$0 == current {
|
||||
print previous
|
||||
exit
|
||||
}
|
||||
{
|
||||
previous = $0
|
||||
}
|
||||
'
|
||||
)"
|
||||
|
||||
{
|
||||
echo "tag=${TAG_NAME}"
|
||||
echo "version=${VERSION}"
|
||||
echo "is_rc=${IS_RC}"
|
||||
echo "basename=${BASENAME}"
|
||||
echo "previous_tag=${PREVIOUS_TAG}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
CURRENT_TAG="${{ steps.release.outputs.tag }}"
|
||||
CURRENT_VERSION="${CURRENT_TAG#v}"
|
||||
PREVIOUS_TAG="${{ steps.release.outputs.previous_tag }}"
|
||||
RELEASE_DATE="$(date -u +%F)"
|
||||
RANGE="${CURRENT_TAG}"
|
||||
|
||||
if [ -n "${PREVIOUS_TAG}" ]; then
|
||||
RANGE="${PREVIOUS_TAG}..${CURRENT_TAG}"
|
||||
fi
|
||||
|
||||
FEATURES="$(git log --pretty=format:'%s (%h)' "${RANGE}" | grep -Ei '^(feat)(\(.+\))?: ' || true)"
|
||||
FIXES="$(git log --pretty=format:'%s (%h)' "${RANGE}" | grep -Ei '^(fix)(\(.+\))?: ' || true)"
|
||||
DOCS="$(git log --pretty=format:'%s (%h)' "${RANGE}" | grep -Ei '^(docs?|doc)(\(.+\))?: ' || true)"
|
||||
|
||||
{
|
||||
if [ -n "${PREVIOUS_TAG}" ]; then
|
||||
echo "# [${CURRENT_VERSION}](https://github.com/${GITHUB_REPOSITORY}/compare/${PREVIOUS_TAG}...${CURRENT_TAG}) (${RELEASE_DATE})"
|
||||
else
|
||||
echo "# ${CURRENT_VERSION} (${RELEASE_DATE})"
|
||||
fi
|
||||
echo
|
||||
if [ "${{ steps.release.outputs.is_rc }}" = "true" ]; then
|
||||
echo "Release candidate for ${{ steps.release.outputs.version }}. This is not an official release."
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ -n "${FEATURES}" ]; then
|
||||
echo "## Features"
|
||||
echo
|
||||
printf '%s\n' "${FEATURES}" | sed 's/^/- /'
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ -n "${FIXES}" ]; then
|
||||
echo "## Fixes"
|
||||
echo
|
||||
printf '%s\n' "${FIXES}" | sed 's/^/- /'
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ -n "${DOCS}" ]; then
|
||||
echo "## Docs"
|
||||
echo
|
||||
printf '%s\n' "${DOCS}" | sed 's/^/- /'
|
||||
echo
|
||||
fi
|
||||
|
||||
if [ -z "${FEATURES}${FIXES}${DOCS}" ]; then
|
||||
echo "## Notes"
|
||||
echo
|
||||
echo "- No feat/fix/doc commits were found in this release range."
|
||||
fi
|
||||
} > RELEASE_NOTES.md
|
||||
|
||||
- name: Create source archives
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
BASENAME="${{ steps.release.outputs.basename }}"
|
||||
mkdir -p dist
|
||||
git archive --format=tar.gz --prefix="${BASENAME}/" -o "dist/${BASENAME}.tar.gz" "${{ steps.release.outputs.tag }}"
|
||||
git archive --format=zip --prefix="${BASENAME}/" -o "dist/${BASENAME}.zip" "${{ steps.release.outputs.tag }}"
|
||||
|
||||
cd dist
|
||||
for archive in "${BASENAME}.tar.gz" "${BASENAME}.zip"; do
|
||||
sha512sum "${archive}" > "${archive}.sha512"
|
||||
done
|
||||
sha512sum --check ./*.sha512
|
||||
|
||||
- name: Publish GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ steps.release.outputs.is_rc }}" = "true" ]; then
|
||||
TITLE="Apache Casbin (Incubating) ${{ steps.release.outputs.version }} (release candidate ${{ steps.release.outputs.tag }})"
|
||||
FLAGS=(--prerelease)
|
||||
else
|
||||
TITLE="Apache Casbin (Incubating) ${{ steps.release.outputs.version }}"
|
||||
FLAGS=(--latest)
|
||||
fi
|
||||
|
||||
gh release create "${{ steps.release.outputs.tag }}" \
|
||||
--verify-tag \
|
||||
--title "${TITLE}" \
|
||||
--notes-file RELEASE_NOTES.md \
|
||||
"${FLAGS[@]}" \
|
||||
dist/*
|
||||
|
||||
- name: Publish to the Go module proxy
|
||||
if: ${{ steps.release.outputs.is_rc == 'false' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
MODULE_PATH="$(awk '/^module /{print $2; exit}' go.mod)"
|
||||
TAG="${{ steps.release.outputs.tag }}"
|
||||
|
||||
# Requesting the version makes the proxy fetch and cache it, which is
|
||||
# what publishes the release to the Go module ecosystem.
|
||||
for attempt in 1 2 3; do
|
||||
if curl --fail --silent --show-error --location \
|
||||
"https://proxy.golang.org/${MODULE_PATH}/@v/${TAG}.info"; then
|
||||
echo
|
||||
echo "Published ${MODULE_PATH}@${TAG} to proxy.golang.org."
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt ${attempt} failed, retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
|
||||
echo "::error::Failed to publish ${MODULE_PATH}@${TAG} to proxy.golang.org."
|
||||
exit 1
|
||||
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
198
.golangci.yml
Normal file
198
.golangci.yml
Normal file
@@ -0,0 +1,198 @@
|
||||
# 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
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- cyclop
|
||||
- dupl
|
||||
- durationcheck
|
||||
- errname
|
||||
- exhaustive
|
||||
- funlen
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoinits
|
||||
- gochecksumtype
|
||||
- gocyclo
|
||||
- godot
|
||||
- gomoddirectives
|
||||
- gomodguard_v2
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- loggercheck
|
||||
- makezero
|
||||
- mirror
|
||||
- musttag
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- noctx
|
||||
- nolintlint
|
||||
- nosprintfhostport
|
||||
- predeclared
|
||||
- promlinter
|
||||
- protogetter
|
||||
- reassign
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sloglint
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- testableexamples
|
||||
- testifylint
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
- whitespace
|
||||
settings:
|
||||
cyclop:
|
||||
max-complexity: 30
|
||||
package-average: 10
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
exhaustive:
|
||||
check:
|
||||
- switch
|
||||
- map
|
||||
exhaustruct:
|
||||
exclude:
|
||||
- ^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$
|
||||
- ^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:
|
||||
lines: 100
|
||||
statements: 50
|
||||
ignore-comments: true
|
||||
gocognit:
|
||||
min-complexity: 20
|
||||
gocritic:
|
||||
settings:
|
||||
captLocal:
|
||||
paramsOnly: false
|
||||
underef:
|
||||
skipRecvDeref: false
|
||||
gomodguard_v2:
|
||||
blocked:
|
||||
- module: github.com/golang/protobuf
|
||||
recommendations:
|
||||
- google.golang.org/protobuf
|
||||
reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
|
||||
- module: github.com/satori/go.uuid
|
||||
recommendations:
|
||||
- github.com/google/uuid
|
||||
reason: satori's package is not maintained
|
||||
- module: github.com/gofrs/uuid
|
||||
recommendations:
|
||||
- github.com/gofrs/uuid/v5
|
||||
reason: gofrs' package was not go module before v5
|
||||
govet:
|
||||
disable:
|
||||
- fieldalignment
|
||||
enable-all: true
|
||||
inamedparam:
|
||||
skip-single-param: true
|
||||
mnd:
|
||||
ignored-functions:
|
||||
- flag.Arg
|
||||
- flag.Duration.*
|
||||
- flag.Float.*
|
||||
- flag.Int.*
|
||||
- flag.Uint.*
|
||||
- os.Chmod
|
||||
- os.Mkdir.*
|
||||
- os.OpenFile
|
||||
- os.WriteFile
|
||||
- prometheus.ExponentialBuckets.*
|
||||
- prometheus.LinearBuckets
|
||||
nakedret:
|
||||
max-func-lines: 0
|
||||
nolintlint:
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
allow-no-explanation:
|
||||
- funlen
|
||||
- gocognit
|
||||
- lll
|
||||
perfsprint:
|
||||
strconcat: false
|
||||
revive:
|
||||
rules:
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
rowserrcheck:
|
||||
packages:
|
||||
- github.com/jmoiron/sqlx
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -ST1003
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- godot
|
||||
source: (noinspection|TODO)
|
||||
- linters:
|
||||
- gocritic
|
||||
source: //noinspection
|
||||
- linters:
|
||||
- bodyclose
|
||||
- dupl
|
||||
- funlen
|
||||
- goconst
|
||||
- gosec
|
||||
- noctx
|
||||
- wrapcheck
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- perfsprint
|
||||
text: fmt.Sprintf can be replaced with string addition
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
max-same-issues: 50
|
||||
formatters:
|
||||
enable:
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
31
.rat-excludes
Normal file
31
.rat-excludes
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
|
||||
# Parser and test fixtures whose contents must remain unchanged.
|
||||
examples/**
|
||||
config/testdata/**
|
||||
|
||||
# Go module metadata. go.sum is generated by the Go toolchain.
|
||||
go.mod
|
||||
go.sum
|
||||
|
||||
# Source-control metadata.
|
||||
**/.gitignore
|
||||
|
||||
# Third-party MIT-licensed golangci-lint configuration. Its license and
|
||||
# attribution are recorded in the root LICENSE file.
|
||||
**/.golangci.yml
|
||||
35
CONTRIBUTING.md
Normal file
35
CONTRIBUTING.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# How to contribute
|
||||
|
||||
The following is a set of guidelines for contributing to Apache Casbin and its libraries, which are hosted at [Apache Casbin organization at Github](https://github.com/apache).
|
||||
|
||||
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.apache.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/apache/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).
|
||||
5
DISCLAIMER
Normal file
5
DISCLAIMER
Normal file
@@ -0,0 +1,5 @@
|
||||
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.
|
||||
229
LICENSE
Normal file
229
LICENSE
Normal file
@@ -0,0 +1,229 @@
|
||||
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.
|
||||
|
||||
THIRD-PARTY COMPONENTS
|
||||
|
||||
The .golangci.yml configuration is based on
|
||||
https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322
|
||||
and is licensed under the MIT License.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Marat Reymers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
29
Makefile
Normal file
29
Makefile
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
|
||||
SHELL = /bin/bash
|
||||
|
||||
default: lint test
|
||||
|
||||
test:
|
||||
go test -race -v ./...
|
||||
|
||||
benchmark:
|
||||
go test -bench=.
|
||||
|
||||
lint:
|
||||
golangci-lint run --verbose
|
||||
8
NOTICE
Normal file
8
NOTICE
Normal file
@@ -0,0 +1,8 @@
|
||||
Apache Casbin (Incubating)
|
||||
Copyright 2026 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
Portions of this software were originally developed by the Casbin project.
|
||||
Copyright 2017-2026 The casbin Authors.
|
||||
278
README.md
Normal file
278
README.md
Normal file
@@ -0,0 +1,278 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
Apache Casbin
|
||||
====
|
||||
|
||||
[](https://github.com/apache/casbin/actions/workflows/golangci-lint.yml)
|
||||
[](https://github.com/apache/casbin/actions/workflows/default.yml)
|
||||
[](https://coveralls.io/github/apache/casbin?branch=master)
|
||||
[](https://pkg.go.dev/github.com/casbin/casbin/v2)
|
||||
[](https://github.com/apache/casbin/releases/latest)
|
||||
[](https://discord.gg/S5UjpzGZjN)
|
||||
|
||||
**News**: still worry about how to write the correct Apache Casbin policy? ``Apache Casbin online editor`` is coming to help! Try it at: https://casbin.apache.org/editor/
|
||||
|
||||

|
||||
|
||||
Apache 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 Apache Casbin:
|
||||
|
||||
| [](https://github.com/apache/casbin) | [](https://github.com/casbin/jcasbin) | [](https://github.com/casbin/node-casbin) | [](https://github.com/php-casbin/php-casbin) |
|
||||
|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| [Casbin](https://github.com/apache/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 Apache 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 Apache 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 Apache Casbin is not designed as a password container. However, Apache Casbin stores the user-role mapping for the RBAC scenario.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
go get github.com/casbin/casbin/v3
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
https://casbin.apache.org/docs/overview
|
||||
|
||||
## Online editor
|
||||
|
||||
You can also use the online editor (https://casbin.apache.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.apache.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, Apache 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
|
||||
|
||||
Apache Casbin provides two sets of APIs to manage permissions:
|
||||
|
||||
- [Management API](https://casbin.apache.org/docs/management-api): the primitive API that provides full support for Apache Casbin policy management.
|
||||
- [RBAC API](https://casbin.apache.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.apache.org/docs/admin-portal) for model management and policy management:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Policy persistence
|
||||
|
||||
https://casbin.apache.org/docs/adapters
|
||||
|
||||
## Policy consistence between multiple nodes
|
||||
|
||||
https://casbin.apache.org/docs/watchers
|
||||
|
||||
## Role manager
|
||||
|
||||
https://casbin.apache.org/docs/role-managers
|
||||
|
||||
## Benchmarks
|
||||
|
||||
https://casbin.apache.org/docs/benchmark
|
||||
|
||||
## Examples
|
||||
|
||||
| Model | Model file | Policy file |
|
||||
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| ACL | [basic_model.conf](https://github.com/apache/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/apache/casbin/blob/master/examples/basic_policy.csv) |
|
||||
| ACL with superuser | [basic_model_with_root.conf](https://github.com/apache/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/apache/casbin/blob/master/examples/basic_policy.csv) |
|
||||
| ACL without users | [basic_model_without_users.conf](https://github.com/apache/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/apache/casbin/blob/master/examples/basic_without_users_policy.csv) |
|
||||
| ACL without resources | [basic_model_without_resources.conf](https://github.com/apache/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/apache/casbin/blob/master/examples/basic_without_resources_policy.csv) |
|
||||
| RBAC | [rbac_model.conf](https://github.com/apache/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/apache/casbin/blob/master/examples/rbac_policy.csv) |
|
||||
| RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/apache/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/apache/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) |
|
||||
| RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/apache/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/apache/casbin/blob/master/examples/rbac_with_domains_policy.csv) |
|
||||
| ABAC | [abac_model.conf](https://github.com/apache/casbin/blob/master/examples/abac_model.conf) | N/A |
|
||||
| RESTful | [keymatch_model.conf](https://github.com/apache/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/apache/casbin/blob/master/examples/keymatch_policy.csv) |
|
||||
| Deny-override | [rbac_model_with_deny.conf](https://github.com/apache/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/apache/casbin/blob/master/examples/rbac_with_deny_policy.csv) |
|
||||
| Priority | [priority_model.conf](https://github.com/apache/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/apache/casbin/blob/master/examples/priority_policy.csv) |
|
||||
|
||||
## Middlewares
|
||||
|
||||
Authz middlewares for web frameworks: https://casbin.apache.org/docs/middlewares
|
||||
|
||||
## Our adopters
|
||||
|
||||
https://casbin.apache.org/docs/adopters
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Please read the [contributing guide](CONTRIBUTING.md).
|
||||
|
||||
## Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
<a href="https://github.com/apache/casbin/graphs/contributors"><img src="https://opencollective.com/casbin/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.dera.page/#apache/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/apache/casbin/issues
|
||||
- https://discord.gg/S5UjpzGZjN
|
||||
201
abac_test.go
Normal file
201
abac_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
224
ai_api.go
Normal file
224
ai_api.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
fmt.Fprintf(&sb, "Subject: %v\n", rvals[0])
|
||||
if len(rvals) > 1 {
|
||||
fmt.Fprintf(&sb, "Object: %v\n", rvals[1])
|
||||
}
|
||||
if len(rvals) > 2 {
|
||||
fmt.Fprintf(&sb, "Action: %v\n", rvals[2])
|
||||
}
|
||||
fmt.Fprintf(&sb, "\nEnforcement Result: %v\n", result)
|
||||
|
||||
// Add matched rules
|
||||
if len(matchedRules) > 0 {
|
||||
sb.WriteString("\nMatched Policy Rules:\n")
|
||||
for _, rule := range matchedRules {
|
||||
fmt.Fprintf(&sb, "- %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 {
|
||||
fmt.Fprintf(&sb, "Matcher (%s): %s\n", key, ast.Value)
|
||||
}
|
||||
}
|
||||
if eff, ok := e.model["e"]; ok {
|
||||
for key, ast := range eff {
|
||||
fmt.Fprintf(&sb, "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 {
|
||||
fmt.Fprintf(&sb, "- %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
|
||||
}
|
||||
253
ai_api_test.go
Normal file
253
ai_api_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
47
biba_test.go
Normal file
47
biba_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
53
blp_test.go
Normal file
53
blp_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
BIN
casbin-logo.png
Normal file
BIN
casbin-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
270
config/config.go
Normal file
270
config/config.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// 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.
|
||||
|
||||
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 ""
|
||||
}
|
||||
93
config/config_test.go
Normal file
93
config/config_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
47
config/testdata/testini.ini
vendored
Normal file
47
config/testdata/testini.ini
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# 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\
|
||||
\
|
||||
34
constant/constants.go
Normal file
34
constant/constants.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
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"
|
||||
)
|
||||
336
constraint_test.go
Normal file
336
constraint_test.go
Normal file
@@ -0,0 +1,336 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
153
detector/default_detector.go
Normal file
153
detector/default_detector.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// 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.
|
||||
|
||||
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
|
||||
}
|
||||
341
detector/default_detector_test.go
Normal file
341
detector/default_detector_test.go
Normal file
@@ -0,0 +1,341 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
detector/detector.go
Normal file
28
detector/detector.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
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
|
||||
}
|
||||
111
effector/default_effector.go
Normal file
111
effector/default_effector.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
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
|
||||
}
|
||||
34
effector/effector.go
Normal file
34
effector/effector.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
1102
enforcer.go
Normal file
1102
enforcer.go
Normal file
File diff suppressed because it is too large
Load Diff
180
enforcer_backslash_test.go
Normal file
180
enforcer_backslash_test.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
188
enforcer_cached.go
Normal file
188
enforcer_cached.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// 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.
|
||||
|
||||
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 {
|
||||
for _, rule := range rules {
|
||||
irule := make([]interface{}, len(rule))
|
||||
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()
|
||||
}
|
||||
224
enforcer_cached_b_test.go
Normal file
224
enforcer_cached_b_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
204
enforcer_cached_gfunction_test.go
Normal file
204
enforcer_cached_gfunction_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
183
enforcer_cached_synced.go
Normal file
183
enforcer_cached_synced.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
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 {
|
||||
for _, rule := range rules {
|
||||
irule := make([]interface{}, len(rule))
|
||||
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
|
||||
}
|
||||
85
enforcer_cached_synced_test.go
Normal file
85
enforcer_cached_synced_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
76
enforcer_cached_test.go
Normal file
76
enforcer_cached_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
861
enforcer_context.go
Normal file
861
enforcer_context.go
Normal file
@@ -0,0 +1,861 @@
|
||||
// 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.
|
||||
|
||||
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.adapter != nil {
|
||||
if contextAdapter, ok := e.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
|
||||
}
|
||||
99
enforcer_context_interface.go
Normal file
99
enforcer_context_interface.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
235
enforcer_context_test.go
Normal file
235
enforcer_context_test.go
Normal file
@@ -0,0 +1,235 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
256
enforcer_distributed.go
Normal file
256
enforcer_distributed.go
Normal file
@@ -0,0 +1,256 @@
|
||||
// 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.
|
||||
|
||||
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
|
||||
}
|
||||
184
enforcer_interface.go
Normal file
184
enforcer_interface.go
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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.
|
||||
|
||||
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{}
|
||||
_ IEnforcer = &SyncedEnforcer{}
|
||||
_ 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)
|
||||
}
|
||||
83
enforcer_json_test.go
Normal file
83
enforcer_json_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
689
enforcer_synced.go
Normal file
689
enforcer_synced.go
Normal file
@@ -0,0 +1,689 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
537
enforcer_synced_test.go
Normal file
537
enforcer_synced_test.go
Normal file
@@ -0,0 +1,537 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
806
enforcer_test.go
Normal file
806
enforcer_test.go
Normal file
@@ -0,0 +1,806 @@
|
||||
// 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.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
124
enforcer_transactional.go
Normal file
124
enforcer_transactional.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
|
||||
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()
|
||||
}
|
||||
288
error_test.go
Normal file
288
error_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
// 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.
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
49
errors/constraint_errors.go
Normal file
49
errors/constraint_errors.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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.
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
33
errors/rbac_errors.go
Normal file
33
errors/rbac_errors.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
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")
|
||||
)
|
||||
11
examples/abac_model.conf
Normal file
11
examples/abac_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
11
examples/abac_not_using_policy_model.conf
Normal file
11
examples/abac_not_using_policy_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
4
examples/abac_rule_effect_policy.csv
Normal file
4
examples/abac_rule_effect_policy.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
p, alice, /data1, read, deny
|
||||
p, alice, /data1, write, allow
|
||||
p, bob, /data2, write, deny
|
||||
p, bob, /data2, read, allow
|
||||
|
11
examples/abac_rule_model.conf
Normal file
11
examples/abac_rule_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/abac_rule_policy.csv
Normal file
2
examples/abac_rule_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, r.sub.Age > 18, /data1, read
|
||||
p, r.sub.Age < 60, /data2, write
|
||||
|
2
examples/basic_inverse_policy.csv
Normal file
2
examples/basic_inverse_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, alice, data1, write
|
||||
p, bob, data2, read
|
||||
|
11
examples/basic_model.conf
Normal file
11
examples/basic_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
11
examples/basic_model_without_spaces.conf
Normal file
11
examples/basic_model_without_spaces.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/basic_policy.csv
Normal file
2
examples/basic_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, alice, data1, read
|
||||
p, bob, data2, write
|
||||
|
11
examples/basic_with_root_model.conf
Normal file
11
examples/basic_with_root_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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"
|
||||
11
examples/basic_without_resources_model.conf
Normal file
11
examples/basic_without_resources_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/basic_without_resources_policy.csv
Normal file
2
examples/basic_without_resources_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, alice, read
|
||||
p, bob, write
|
||||
|
11
examples/basic_without_users_model.conf
Normal file
11
examples/basic_without_users_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/basic_without_users_policy.csv
Normal file
2
examples/basic_without_users_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, data1, read
|
||||
p, data2, write
|
||||
|
14
examples/biba_model.conf
Normal file
14
examples/biba_model.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[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)
|
||||
14
examples/blp_model.conf
Normal file
14
examples/blp_model.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[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)
|
||||
12
examples/comment_model.conf
Normal file
12
examples/comment_model.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
[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
|
||||
11
examples/error/error_model.conf
Normal file
11
examples/error/error_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/error/error_policy.csv
Normal file
2
examples/error/error_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, alice, data1, read
|
||||
bob, data2, write
|
||||
|
11
examples/eval_operator_model.conf
Normal file
11
examples/eval_operator_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
1
examples/eval_operator_policy.csv
Normal file
1
examples/eval_operator_policy.csv
Normal file
@@ -0,0 +1 @@
|
||||
p, r.sub == 'admin' || false, r.obj == 'users', write
|
||||
|
11
examples/glob_model.conf
Normal file
11
examples/glob_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
4
examples/glob_policy.csv
Normal file
4
examples/glob_policy.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
p, u1, /foo/*, read
|
||||
p, u2, /foo*, read
|
||||
p, u3, /*/foo/*, read
|
||||
p, u4, *, read
|
||||
|
11
examples/ipmatch_model.conf
Normal file
11
examples/ipmatch_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/ipmatch_policy.csv
Normal file
2
examples/ipmatch_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, 192.168.2.0/24, data1, read
|
||||
p, 10.0.0.0/16, data2, write
|
||||
|
11
examples/keyget2_model.conf
Normal file
11
examples/keyget2_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
11
examples/keyget_model.conf
Normal file
11
examples/keyget_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
11
examples/keymatch2_model.conf
Normal file
11
examples/keymatch2_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
2
examples/keymatch2_policy.csv
Normal file
2
examples/keymatch2_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, alice, /alice_data/:resource, GET
|
||||
p, alice, /alice_data2/:id/using/:resId, GET
|
||||
|
11
examples/keymatch_custom_model.conf
Normal file
11
examples/keymatch_custom_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
11
examples/keymatch_model.conf
Normal file
11
examples/keymatch_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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)
|
||||
7
examples/keymatch_policy.csv
Normal file
7
examples/keymatch_policy.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
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)
|
||||
|
14
examples/keymatch_with_rbac_in_domain.conf
Normal file
14
examples/keymatch_with_rbac_in_domain.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[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)
|
||||
6
examples/keymatch_with_rbac_in_domain.csv
Normal file
6
examples/keymatch_with_rbac_in_domain.csv
Normal file
@@ -0,0 +1,6 @@
|
||||
g, can_manage, can_use, *
|
||||
|
||||
p, can_manage, engines/*, *, (pause)|(resume)
|
||||
p, can_use, engines/*, *, (attach)|(detach)
|
||||
|
||||
g, Username==test2, can_manage, engines/engine1
|
||||
|
14
examples/lbac_model.conf
Normal file
14
examples/lbac_model.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[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)
|
||||
19
examples/multiple_policy_definitions_model.conf
Normal file
19
examples/multiple_policy_definitions_model.conf
Normal file
@@ -0,0 +1,19 @@
|
||||
[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
|
||||
5
examples/multiple_policy_definitions_policy.csv
Normal file
5
examples/multiple_policy_definitions_policy.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
|
14
examples/object_conditions_model.conf
Normal file
14
examples/object_conditions_model.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
[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
|
||||
5
examples/object_conditions_policy.csv
Normal file
5
examples/object_conditions_policy.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
||||
|
16
examples/orbac_model.conf
Normal file
16
examples/orbac_model.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
[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
|
||||
25
examples/orbac_policy.csv
Normal file
25
examples/orbac_policy.csv
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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
|
||||
|
11
examples/pbac_model.conf
Normal file
11
examples/pbac_model.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
2
examples/pbac_policy.csv
Normal file
2
examples/pbac_policy.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
p, r.sub.Role == 'admin', r.obj.Type == 'doc', read
|
||||
p, r.sub.Age >= 18, r.obj.Type == 'video', play
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user