[
  {
    "path": ".asf.yaml",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n\n# For more information, see https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features.\n\ngithub:\n  description: >-\n    Apache Casbin: an authorization library that supports access control models like ACL, RBAC, ABAC.\n  homepage: https://casbin.apache.org/\n  dependabot_alerts:  true\n  dependabot_updates: false\n\nnotifications:\n  commits: commits@casbin.apache.org\n\n"
  },
  {
    "path": ".github/scripts/benchmark_formatter.py",
    "content": "import pathlib, re, sys\n\ntry:\n    p = pathlib.Path(\"comparison.md\")\n    if not p.exists():\n        print(\"comparison.md not found, skipping post-processing.\")\n        sys.exit(0)\n\n    lines = p.read_text(encoding=\"utf-8\").splitlines()\n    processed_lines = []\n    in_code = False\n    def strip_worker_suffix(text: str) -> str:\n        return re.sub(r'(\\S+?)-\\d+(\\s|$)', r'\\1\\2', text)\n\n    def get_icon(diff_val: float) -> str:\n        if diff_val > 10:\n            return \"🐌\"\n        if diff_val < -10:\n            return \"🚀\"\n        return \"➡️\"\n\n    def clean_superscripts(text: str) -> str:\n        return re.sub(r'[¹²³⁴⁵⁶⁷⁸⁹⁰]', '', text)\n\n    def parse_val(token: str):\n        if '%' in token or '=' in token:\n            return None\n        token = clean_superscripts(token)\n        token = token.split('±')[0].strip()\n        token = token.split('(')[0].strip()\n        if not token:\n            return None\n\n        m = re.match(r'^([-+]?\\d*\\.?\\d+)([a-zA-Zµ]+)?$', token)\n        if not m:\n            return None\n        try:\n            val = float(m.group(1))\n        except ValueError:\n            return None\n        suffix = (m.group(2) or \"\").replace(\"µ\", \"u\")\n        multipliers = {\n            \"n\": 1e-9,\n            \"ns\": 1e-9,\n            \"u\": 1e-6,\n            \"us\": 1e-6,\n            \"m\": 1e-3,\n            \"ms\": 1e-3,\n            \"s\": 1.0,\n            \"k\": 1e3,\n            \"K\": 1e3,\n            \"M\": 1e6,\n            \"G\": 1e9,\n            \"Ki\": 1024.0,\n            \"Mi\": 1024.0**2,\n            \"Gi\": 1024.0**3,\n            \"Ti\": 1024.0**4,\n            \"B\": 1.0,\n            \"B/op\": 1.0,\n            \"C\": 1.0,  # tolerate degree/unit markers that don't affect ratio\n        }\n        return val * multipliers.get(suffix, 1.0)\n\n    def extract_two_numbers(tokens):\n        found = []\n        for t in tokens[1:]:  # skip name\n            if t in {\"±\", \"∞\", \"~\", \"│\", \"│\"}:\n                continue\n            if '%' in t or '=' in t:\n                continue\n            val = parse_val(t)\n            if val is not None:\n                found.append(val)\n                if len(found) == 2:\n                    break\n        return found\n\n    # Pass 0: \n    # 1. find a header line with pipes to derive alignment hint\n    # 2. calculate max content width to ensure right-most alignment\n    max_content_width = 0\n    \n    for line in lines:\n        if line.strip() == \"```\":\n            in_code = not in_code\n            continue\n        if not in_code:\n            continue\n            \n        # Skip footnotes/meta for width calculation\n        if re.match(r'^\\s*[¹²³⁴⁵⁶⁷⁸⁹⁰]', line) or re.search(r'need\\s*>?=\\s*\\d+\\s+samples', line):\n            continue\n        if not line.strip() or line.strip().startswith(('goos:', 'goarch:', 'pkg:', 'cpu:')):\n            continue\n        # Header lines are handled separately in Pass 1\n        if '│' in line and ('vs base' in line or 'old' in line or 'new' in line):\n            continue\n            \n        # It's likely a data line\n        # Check if it has an existing percentage we might move/align\n        curr_line = strip_worker_suffix(line).rstrip()\n        pct_match = re.search(r'([+-]?\\d+\\.\\d+)%', curr_line)\n        if pct_match:\n            # If we are going to realign this, we count width up to the percentage\n            w = len(curr_line[:pct_match.start()].rstrip())\n        else:\n            w = len(curr_line)\n        \n        if w > max_content_width:\n            max_content_width = w\n\n    # Calculate global alignment target for Diff column\n    # Ensure target column is beyond the longest line with some padding\n    diff_col_start = max_content_width + 4\n    \n    # Calculate right boundary (pipe) position\n    # Diff column width ~12 chars (e.g. \"+100.00% 🚀\")\n    right_boundary = diff_col_start + 14\n\n    # Reset code fence tracking state for Pass 1\n    in_code = False\n    for line in lines:\n\n        if line.strip() == \"```\":\n            in_code = not in_code\n            processed_lines.append(line)\n            continue\n\n        if not in_code:\n            processed_lines.append(line)\n            continue\n\n        # footnotes keep untouched\n        if re.match(r'^\\s*[¹²³⁴⁵⁶⁷⁸⁹⁰]', line) or re.search(r'need\\s*>?=\\s*\\d+\\s+samples', line):\n            processed_lines.append(line)\n            continue\n\n        # header lines: ensure last column labeled Diff and force alignment\n        if '│' in line and ('vs base' in line or 'old' in line or 'new' in line):\n            # Strip trailing pipe and whitespace\n            stripped_header = line.rstrip().rstrip('│').rstrip()\n            \n            # If \"vs base\" is present, ensure we don't duplicate \"Diff\" if it's already there\n            # But we want to enforce OUR alignment, so we might strip existing Diff\n            stripped_header = re.sub(r'\\s+Diff\\s*$', '', stripped_header, flags=re.IGNORECASE)\n            stripped_header = re.sub(r'\\s+Delta\\b', '', stripped_header, flags=re.IGNORECASE)\n\n            # Pad to diff_col_start\n            if len(stripped_header) < diff_col_start:\n                new_header = stripped_header + \" \" * (diff_col_start - len(stripped_header))\n            else:\n                new_header = stripped_header + \"  \"\n\n            # Add Diff column header if it's the second header row (vs base)\n            if 'vs base' in line:\n                new_header += \"Diff\"\n            \n            # Add closing pipe at the right boundary\n            current_len = len(new_header)\n            if current_len < right_boundary:\n                new_header += \" \" * (right_boundary - current_len)\n            \n            new_header += \"│\"\n            processed_lines.append(new_header)\n            continue\n\n        # non-data meta lines\n        if not line.strip() or line.strip().startswith(('goos:', 'goarch:', 'pkg:')):\n            processed_lines.append(line)\n            continue\n\n        line = strip_worker_suffix(line)\n        tokens = line.split()\n        if not tokens:\n            processed_lines.append(line)\n            continue\n\n        numbers = extract_two_numbers(tokens)\n        pct_match = re.search(r'([+-]?\\d+\\.\\d+)%', line)\n\n        # Helper to align and append\n        def append_aligned(left_part, content):\n            if len(left_part) < diff_col_start:\n                aligned = left_part + \" \" * (diff_col_start - len(left_part))\n            else:\n                aligned = left_part + \"  \"\n            \n            # Ensure content doesn't exceed right boundary (visual check only, we don't truncate)\n            # But users asked not to exceed header pipe.\n            # Header pipe is at right_boundary.\n            # Content starts at diff_col_start.\n            # So content length should be <= right_boundary - diff_col_start\n            return f\"{aligned}{content}\"\n\n        # Special handling for geomean when values missing or zero\n        is_geomean = tokens[0] == \"geomean\"\n        if is_geomean and (len(numbers) < 2 or any(v == 0 for v in numbers)) and not pct_match:\n            leading = re.match(r'^\\s*', line).group(0)\n            left = f\"{leading}geomean\"\n            processed_lines.append(append_aligned(left, \"n/a (has zero)\"))\n            continue\n\n        # when both values are zero, force diff = 0 and align\n        if len(numbers) == 2 and numbers[0] == 0 and numbers[1] == 0:\n            diff_val = 0.0\n            icon = get_icon(diff_val)\n            left = line.rstrip()\n            processed_lines.append(append_aligned(left, f\"{diff_val:+.2f}% {icon}\"))\n            continue\n\n        # recompute diff when we have two numeric values\n        if len(numbers) == 2 and numbers[0] != 0:\n            diff_val = (numbers[1] - numbers[0]) / numbers[0] * 100\n            icon = get_icon(diff_val)\n\n            left = line\n            if pct_match:\n                left = line[:pct_match.start()].rstrip()\n            else:\n                left = line.rstrip()\n\n            processed_lines.append(append_aligned(left, f\"{diff_val:+.2f}% {icon}\"))\n            continue\n\n        # fallback: align existing percentage to Diff column and (re)append icon\n        if pct_match:\n            try:\n                pct_val = float(pct_match.group(1))\n                icon = get_icon(pct_val)\n\n                left = line[:pct_match.start()].rstrip()\n                suffix = line[pct_match.end():]\n                # Remove any existing icon after the percentage to avoid duplicates\n                suffix = re.sub(r'\\s*(🐌|🚀|➡️)', '', suffix)\n\n                processed_lines.append(append_aligned(left, f\"{pct_val:+.2f}% {icon}{suffix}\"))\n            except ValueError:\n                processed_lines.append(line)\n            continue\n\n        # If we cannot parse numbers or percentages, keep the original (only worker suffix stripped)\n        processed_lines.append(line)\n\n    p.write_text(\"\\n\".join(processed_lines) + \"\\n\", encoding=\"utf-8\")\n\nexcept Exception as e:\n    print(f\"Error post-processing comparison.md: {e}\")\n    sys.exit(1)\n"
  },
  {
    "path": ".github/scripts/download_artifact.js",
    "content": "module.exports = async ({github, context, core}) => {\n  try {\n    const artifacts = await github.rest.actions.listWorkflowRunArtifacts({\n      owner: context.repo.owner,\n      repo: context.repo.repo,\n      run_id: context.payload.workflow_run.id,\n    });\n\n    const matchArtifact = artifacts.data.artifacts.find((artifact) => {\n      return artifact.name == \"benchmark-results\";\n    });\n\n    if (!matchArtifact) {\n      core.setFailed(\"No artifact named 'benchmark-results' found.\");\n      return;\n    }\n\n    const download = await github.rest.actions.downloadArtifact({\n      owner: context.repo.owner,\n      repo: context.repo.repo,\n      artifact_id: matchArtifact.id,\n      archive_format: 'zip',\n    });\n\n    const fs = require('fs');\n    const path = require('path');\n    const workspace = process.env.GITHUB_WORKSPACE;\n    fs.writeFileSync(path.join(workspace, 'benchmark-results.zip'), Buffer.from(download.data));\n  } catch (error) {\n    core.setFailed(`Failed to download artifact: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": ".github/scripts/post_comment.js",
    "content": "module.exports = async ({github, context, core}) => {\n  const fs = require('fs');\n  \n  // Validate pr_number.txt\n  if (!fs.existsSync('pr_number.txt')) {\n    core.setFailed(\"Required artifact file 'pr_number.txt' was not found in the workspace.\");\n    return;\n  }\n  const prNumberContent = fs.readFileSync('pr_number.txt', 'utf8').trim();\n  const issue_number = parseInt(prNumberContent, 10);\n  if (!Number.isFinite(issue_number) || issue_number <= 0) {\n     core.setFailed('Invalid PR number in pr_number.txt: \"' + prNumberContent + '\"');\n     return;\n  }\n\n  // Validate comparison.md\n  if (!fs.existsSync('comparison.md')) {\n    core.setFailed(\"Required artifact file 'comparison.md' was not found in the workspace.\");\n    return;\n  }\n  let comparison;\n  try {\n    comparison = fs.readFileSync('comparison.md', 'utf8');\n  } catch (error) {\n    core.setFailed(\"Failed to read 'comparison.md': \" + error.message);\n    return;\n  }\n\n  // Find existing comment\n  const { data: comments } = await github.rest.issues.listComments({\n    owner: context.repo.owner,\n    repo: context.repo.repo,\n    issue_number: issue_number,\n  });\n\n  const botComment = comments.find(comment =>\n    comment.user.type === 'Bot' &&\n    comment.body.includes('Benchmark Comparison')\n  );\n\n  const footer = '<sub>🤖 This comment will be automatically updated with the latest benchmark results.</sub>';\n  const commentBody = `${comparison}\\n\\n${footer}`;\n\n  if (botComment) {\n    await github.rest.issues.updateComment({\n      owner: context.repo.owner,\n      repo: context.repo.repo,\n      comment_id: botComment.id,\n      body: commentBody\n    });\n  } else {\n    await github.rest.issues.createComment({\n      owner: context.repo.owner,\n      repo: context.repo.repo,\n      issue_number: issue_number,\n      body: commentBody\n    });\n  }\n};\n"
  },
  {
    "path": ".github/semantic.yml",
    "content": "# Always validate the PR title AND all the commits\ntitleAndCommits: true"
  },
  {
    "path": ".github/workflows/comment.yml",
    "content": "name: Post Benchmark Comment\n\non:\n  workflow_run:\n    workflows: [\"Performance Comparison for Pull Requests\"]\n    types:\n      - completed\n\npermissions:\n  pull-requests: write\n\njobs:\n  comment:\n    runs-on: ubuntu-latest\n    if: >\n      github.event.workflow_run.event == 'pull_request' &&\n      github.event.workflow_run.conclusion == 'success'\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v4\n\n      - name: 'Download artifact'\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const script = require('./.github/scripts/download_artifact.js')\n            await script({github, context, core})\n\n      - name: 'Unzip artifact'\n        run: unzip benchmark-results.zip\n\n      - name: 'Post comment'\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const script = require('./.github/scripts/post_comment.js')\n            await script({github, context, core})\n"
  },
  {
    "path": ".github/workflows/default.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        go: ['1.21']\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Run go test\n        run:  make test\n\n  benchmark:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        go: ['1.21']\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Run go test bench\n        run:  make benchmark\n\n  semantic-release:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - name: Setup Node.js\n        uses: actions/setup-node@v3\n        with:\n          node-version: 20\n\n      - name: Run semantic-release\n        if: github.repository == 'casbin/casbin' && github.event_name == 'push'\n        run: make release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "content": "name: golangci-lint\n\non:\n  push:\n    branches:\n      - master\n      - main\n  pull_request:\n\njobs:\n  golangci:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '1.21'\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v4\n        with:\n          version: v1.56.2"
  },
  {
    "path": ".github/workflows/performance-pr.yml",
    "content": "name: Performance Comparison for Pull Requests\n\non:\n  pull_request:\n    branches: [master]\n\njobs:\n  benchmark-pr:\n    name: Performance benchmark comparison\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout PR branch\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: 'stable'\n\n      # Save commit SHAs for display\n      - name: Save commit info\n        id: commits\n        run: |\n          BASE_SHA=\"${{ github.event.pull_request.base.sha }}\"\n          HEAD_SHA=\"${{ github.event.pull_request.head.sha }}\"\n          echo \"base_short=${BASE_SHA:0:7}\" >> $GITHUB_OUTPUT\n          echo \"head_short=${HEAD_SHA:0:7}\" >> $GITHUB_OUTPUT\n\n      # Run benchmark on PR branch\n      - name: Run benchmark on PR branch\n        run: |\n          go test -bench '.' -benchtime=2s -benchmem ./... | tee pr-bench.txt\n\n      # Checkout base branch and run benchmark\n      - name: Checkout base branch\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.base.sha }}\n          clean: false\n          path: base\n\n      - name: Run benchmark on base branch\n        working-directory: base\n        run: |\n          go test -bench '.' -benchtime=2s -benchmem ./... | \\\n            tee ../base-bench.txt\n\n      # Install benchstat for comparison\n      - name: Install benchstat\n        run: go install golang.org/x/perf/cmd/benchstat@latest\n\n      # Compare benchmarks using benchstat\n      - name: Compare benchmarks with benchstat\n        id: benchstat\n        run: |\n          cat > comparison.md << 'EOF'\n          ## Benchmark Comparison\n\n          Comparing base branch (`${{ steps.commits.outputs.base_short }}`)\n          vs PR branch (`${{ steps.commits.outputs.head_short }}`)\n\n          ```\n          EOF\n          benchstat base-bench.txt pr-bench.txt >> comparison.md || true\n          echo '```' >> comparison.md\n\n          # Post-process to append percentage + emoji column (🚀 faster < -10%, 🐌 slower > +10%, otherwise ➡️)\n          if [ ! -f comparison.md ]; then\n            echo \"comparison.md not found after benchstat.\" >&2\n            exit 1\n          fi\n          python3 .github/scripts/benchmark_formatter.py\n\n      # Save PR number\n      - name: Save PR number\n        run: |\n          PR_NUMBER=\"${{ github.event.pull_request.number }}\"\n          if [ -z \"$PR_NUMBER\" ]; then\n            echo \"Error: Pull request number is not available in event payload.\" >&2\n            exit 1\n          fi\n          echo \"$PR_NUMBER\" > pr_number.txt\n\n      # Upload benchmark results\n      - name: Upload benchmark results\n        uses: actions/upload-artifact@v4\n        with:\n          name: benchmark-results\n          path: |\n            comparison.md\n            pr_number.txt\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n.idea/\n*.iml\n\n# vendor files\nvendor\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# Based on https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322\n# This code is licensed under the terms of the MIT license https://opensource.org/license/mit\n# Copyright (c) 2021 Marat Reymers\n\n## Golden config for golangci-lint v1.56.2\n#\n# This is the best config for golangci-lint based on my experience and opinion.\n# It is very strict, but not extremely strict.\n# Feel free to adapt and change it for your needs.\n\nrun:\n  # Timeout for analysis, e.g. 30s, 5m.\n  # Default: 1m\n  timeout: 3m\n\n\n# This file contains only configs which differ from defaults.\n# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml\nlinters-settings:\n  cyclop:\n    # The maximal code complexity to report.\n    # Default: 10\n    max-complexity: 30\n    # The maximal average package complexity.\n    # If it's higher than 0.0 (float) the check is enabled\n    # Default: 0.0\n    package-average: 10.0\n\n  errcheck:\n    # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.\n    # Such cases aren't reported by default.\n    # Default: false\n    check-type-assertions: true\n\n  exhaustive:\n    # Program elements to check for exhaustiveness.\n    # Default: [ switch ]\n    check:\n      - switch\n      - map\n\n  exhaustruct:\n    # List of regular expressions to exclude struct packages and their names from checks.\n    # Regular expressions must match complete canonical struct package/name/structname.\n    # Default: []\n    exclude:\n      # std libs\n      - \"^net/http.Client$\"\n      - \"^net/http.Cookie$\"\n      - \"^net/http.Request$\"\n      - \"^net/http.Response$\"\n      - \"^net/http.Server$\"\n      - \"^net/http.Transport$\"\n      - \"^net/url.URL$\"\n      - \"^os/exec.Cmd$\"\n      - \"^reflect.StructField$\"\n      # public libs\n      - \"^github.com/Shopify/sarama.Config$\"\n      - \"^github.com/Shopify/sarama.ProducerMessage$\"\n      - \"^github.com/mitchellh/mapstructure.DecoderConfig$\"\n      - \"^github.com/prometheus/client_golang/.+Opts$\"\n      - \"^github.com/spf13/cobra.Command$\"\n      - \"^github.com/spf13/cobra.CompletionOptions$\"\n      - \"^github.com/stretchr/testify/mock.Mock$\"\n      - \"^github.com/testcontainers/testcontainers-go.+Request$\"\n      - \"^github.com/testcontainers/testcontainers-go.FromDockerfile$\"\n      - \"^golang.org/x/tools/go/analysis.Analyzer$\"\n      - \"^google.golang.org/protobuf/.+Options$\"\n      - \"^gopkg.in/yaml.v3.Node$\"\n\n  funlen:\n    # Checks the number of lines in a function.\n    # If lower than 0, disable the check.\n    # Default: 60\n    lines: 100\n    # Checks the number of statements in a function.\n    # If lower than 0, disable the check.\n    # Default: 40\n    statements: 50\n    # Ignore comments when counting lines.\n    # Default false\n    ignore-comments: true\n\n  gocognit:\n    # Minimal code complexity to report.\n    # Default: 30 (but we recommend 10-20)\n    min-complexity: 20\n\n  gocritic:\n    # Settings passed to gocritic.\n    # The settings key is the name of a supported gocritic checker.\n    # The list of supported checkers can be find in https://go-critic.github.io/overview.\n    settings:\n      captLocal:\n        # Whether to restrict checker to params only.\n        # Default: true\n        paramsOnly: false\n      underef:\n        # Whether to skip (*x).method() calls where x is a pointer receiver.\n        # Default: true\n        skipRecvDeref: false\n\n  gomnd:\n    # List of function patterns to exclude from analysis.\n    # Values always ignored: `time.Date`,\n    # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`,\n    # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`.\n    # Default: []\n    ignored-functions:\n      - flag.Arg\n      - flag.Duration.*\n      - flag.Float.*\n      - flag.Int.*\n      - flag.Uint.*\n      - os.Chmod\n      - os.Mkdir.*\n      - os.OpenFile\n      - os.WriteFile\n      - prometheus.ExponentialBuckets.*\n      - prometheus.LinearBuckets\n\n  gomodguard:\n    blocked:\n      # List of blocked modules.\n      # Default: []\n      modules:\n        - github.com/golang/protobuf:\n            recommendations:\n              - google.golang.org/protobuf\n            reason: \"see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules\"\n        - github.com/satori/go.uuid:\n            recommendations:\n              - github.com/google/uuid\n            reason: \"satori's package is not maintained\"\n        - github.com/gofrs/uuid:\n            recommendations:\n              - github.com/gofrs/uuid/v5\n            reason: \"gofrs' package was not go module before v5\"\n\n  govet:\n    # Enable all analyzers.\n    # Default: false\n    enable-all: true\n    # Disable analyzers by name.\n    # Run `go tool vet help` to see all analyzers.\n    # Default: []\n    disable:\n      - fieldalignment # too strict\n    # Settings per analyzer.\n    settings:\n      shadow:\n        # Whether to be strict about shadowing; can be noisy.\n        # Default: false\n        #strict: true\n\n  inamedparam:\n    # Skips check for interface methods with only a single parameter.\n    # Default: false\n    skip-single-param: true\n\n  nakedret:\n    # Make an issue if func has more lines of code than this setting, and it has naked returns.\n    # Default: 30\n    max-func-lines: 0\n\n  nolintlint:\n    # Exclude following linters from requiring an explanation.\n    # Default: []\n    allow-no-explanation: [ funlen, gocognit, lll ]\n    # Enable to require an explanation of nonzero length after each nolint directive.\n    # Default: false\n    require-explanation: true\n    # Enable to require nolint directives to mention the specific linter being suppressed.\n    # Default: false\n    require-specific: true\n\n  perfsprint:\n    # Optimizes into strings concatenation.\n    # Default: true\n    strconcat: false\n\n  rowserrcheck:\n    # database/sql is always checked\n    # Default: []\n    packages:\n      - github.com/jmoiron/sqlx\n\n  tenv:\n    # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.\n    # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.\n    # Default: false\n    all: true\n\n  stylecheck:\n    # STxxxx checks in https://staticcheck.io/docs/configuration/options/#checks\n    # Default: [\"*\"]\n    checks: [\"all\", \"-ST1003\"]\n\n  revive:\n    rules:\n      # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter\n      - name: unused-parameter\n        disabled: true\n\nlinters:\n  disable-all: true\n  enable:\n    ## enabled by default\n    #- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases\n    - gosimple # specializes in simplifying a code\n    - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string\n    - ineffassign # detects when assignments to existing variables are not used\n    - staticcheck # is a go vet on steroids, applying a ton of static analysis checks\n    - typecheck # like the front-end of a Go compiler, parses and type-checks Go code\n    - unused # checks for unused constants, variables, functions and types\n    ## disabled by default\n    - asasalint # checks for pass []any as any in variadic func(...any)\n    - asciicheck # checks that your code does not contain non-ASCII identifiers\n    - bidichk # checks for dangerous unicode character sequences\n    - bodyclose # checks whether HTTP response body is closed successfully\n    - cyclop # checks function and package cyclomatic complexity\n    - dupl # tool for code clone detection\n    - durationcheck # checks for two durations multiplied together\n    - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error\n    #- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13\n    - execinquery # checks query string in Query function which reads your Go src files and warning it finds\n    - exhaustive # checks exhaustiveness of enum switch statements\n    - exportloopref # checks for pointers to enclosing loop variables\n    #- forbidigo # forbids identifiers\n    - funlen # tool for detection of long functions\n    - gocheckcompilerdirectives # validates go compiler directive comments (//go:)\n    #- gochecknoglobals # checks that no global variables exist\n    - gochecknoinits # checks that no init functions are present in Go code\n    - gochecksumtype # checks exhaustiveness on Go \"sum types\"\n    #- gocognit # computes and checks the cognitive complexity of functions\n    #- goconst # finds repeated strings that could be replaced by a constant\n    #- gocritic # provides diagnostics that check for bugs, performance and style issues\n    - gocyclo # computes and checks the cyclomatic complexity of functions\n    - godot # checks if comments end in a period\n    - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt\n    #- gomnd # detects magic numbers\n    - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod\n    - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations\n    - goprintffuncname # checks that printf-like functions are named with f at the end\n    - gosec # inspects source code for security problems\n    #- lll # reports long lines\n    - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap)\n    - makezero # finds slice declarations with non-zero initial length\n    - mirror # reports wrong mirror patterns of bytes/strings usage\n    - musttag # enforces field tags in (un)marshaled structs\n    - nakedret # finds naked returns in functions greater than a specified function length\n    - nestif # reports deeply nested if statements\n    - nilerr # finds the code that returns nil even if it checks that the error is not nil\n    #- nilnil # checks that there is no simultaneous return of nil error and an invalid value\n    - noctx # finds sending http request without context.Context\n    - nolintlint # reports ill-formed or insufficient nolint directives\n    #- nonamedreturns # reports all named returns\n    - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL\n    #- perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative\n    - predeclared # finds code that shadows one of Go's predeclared identifiers\n    - promlinter # checks Prometheus metrics naming via promlint\n    - protogetter # reports direct reads from proto message fields when getters should be used\n    - reassign # checks that package variables are not reassigned\n    - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint\n    - rowserrcheck # checks whether Err of rows is checked successfully\n    - sloglint # ensure consistent code style when using log/slog\n    - spancheck # checks for mistakes with OpenTelemetry/Census spans\n    - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed\n    - stylecheck # is a replacement for golint\n    - tenv # detects using os.Setenv instead of t.Setenv since Go1.17\n    - testableexamples # checks if examples are testable (have an expected output)\n    - testifylint # checks usage of github.com/stretchr/testify\n    #- testpackage # makes you use a separate _test package\n    - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes\n    - unconvert # removes unnecessary type conversions\n    #- unparam # reports unused function parameters\n    - usestdlibvars # detects the possibility to use variables/constants from the Go standard library\n    - wastedassign # finds wasted assignment statements\n    - whitespace # detects leading and trailing whitespace\n\n    ## you may want to enable\n    #- decorder # checks declaration order and count of types, constants, variables and functions\n    #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized\n    #- gci # controls golang package import order and makes it always deterministic\n    #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega\n    #- godox # detects FIXME, TODO and other comment keywords\n    #- goheader # checks is file header matches to pattern\n    #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters\n    #- interfacebloat # checks the number of methods inside an interface\n    #- ireturn # accept interfaces, return concrete types\n    #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated\n    #- tagalign # checks that struct tags are well aligned\n    #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope\n    #- wrapcheck # checks that errors returned from external packages are wrapped\n    #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event\n\n    ## disabled\n    #- containedctx # detects struct contained context.Context field\n    #- contextcheck # [too many false positives] checks the function whether use a non-inherited context\n    #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages\n    #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())\n    #- dupword # [useless without config] checks for duplicate words in the source code\n    #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted\n    #- forcetypeassert # [replaced by errcheck] finds forced type assertions\n    #- goerr113 # [too strict] checks the errors handling expressions\n    #- gofmt # [replaced by goimports] checks whether code was gofmt-ed\n    #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed\n    #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase\n    #- grouper # analyzes expression groups\n    #- importas # enforces consistent import aliases\n    #- maintidx # measures the maintainability index of each function\n    #- misspell # [useless] finds commonly misspelled English words in comments\n    #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity\n    #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test\n    #- tagliatelle # checks the struct tags\n    #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers\n    #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines\n\n    ## deprecated\n    #- deadcode # [deprecated, replaced by unused] finds unused code\n    #- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized\n    #- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes\n    #- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible\n    #- interfacer # [deprecated] suggests narrower interface types\n    #- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted\n    #- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name\n    #- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs\n    #- structcheck # [deprecated, replaced by unused] finds unused struct fields\n    #- varcheck # [deprecated, replaced by unused] finds unused global variables and constants\n\n\nissues:\n  # Maximum count of issues with the same text.\n  # Set to 0 to disable.\n  # Default: 3\n  max-same-issues: 50\n\n  exclude-rules:\n    - source: \"(noinspection|TODO)\"\n      linters: [ godot ]\n    - source: \"//noinspection\"\n      linters: [ gocritic ]\n    - path: \"_test\\\\.go\"\n      linters:\n        - bodyclose\n        - dupl\n        - funlen\n        - goconst\n        - gosec\n        - noctx\n        - wrapcheck\n    # TODO: remove after PR is released https://github.com/golangci/golangci-lint/pull/4386\n    - text: \"fmt.Sprintf can be replaced with string addition\"\n      linters: [ perfsprint ]"
  },
  {
    "path": ".releaserc.json",
    "content": "{\n  \"debug\": true,\n  \"branches\": [\n    \"+([0-9])?(.{+([0-9]),x}).x\",\n    \"master\",\n    {\n      \"name\": \"beta\",\n      \"prerelease\": true\n    }\n  ],\n  \"plugins\": [\n    \"@semantic-release/commit-analyzer\",\n    \"@semantic-release/release-notes-generator\",\n    \"@semantic-release/github\"\n  ]\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute\n\nThe following is a set of guidelines for contributing to casbin and its libraries, which are hosted at [casbin organization at Github](https://github.com/casbin).\n\nThis 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.\n\n## Questions\n\n- We do our best to have an [up-to-date documentation](https://casbin.org/docs/overview)\n- [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.\n- You can also join our [Discord](https://discord.gg/S5UjpzGZjN).\n\n## Reporting issues\n\nReporting issues are a great way to contribute to the project. We are perpetually grateful about a well-written, through bug report.\n\nBefore raising a new issue, check our [issue list](https://github.com/casbin/casbin/issues) to determine if it already contains the problem that you are facing.\n\nA 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:\n\nWhat were you trying to achieve?\nWhat are the expected results?\nWhat are the received results?\nWhat are the steps to reproduce the issue?\nIn what environment did you encounter the issue?\n\nFeature requests can also be submitted as issues.\n\n## Pull requests\n\nGood pull requests (e.g. patches, improvements, new features) are a fantastic help. They should remain focused in scope and avoid unrelated commits.\n\nPlease 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.\n\nFirst 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).\n"
  },
  {
    "path": "DISCLAIMER",
    "content": "Apache Casbin(Incubating) is an effort undergoing incubation at the Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC.\n\nIncubation 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.\n\nWhile 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.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL = /bin/bash\nexport PATH := $(shell yarn global bin):$(PATH)\n\ndefault: lint test\n\ntest:\n\tgo test -race -v ./...\n\nbenchmark:\n\tgo test -bench=.\n\nlint:\n\tgolangci-lint run --verbose\n\nrelease:\n\tnpx semantic-release@v19.0.2\n"
  },
  {
    "path": "README.md",
    "content": "Casbin\n====\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/casbin/casbin)](https://goreportcard.com/report/github.com/casbin/casbin)\n[![Build](https://github.com/casbin/casbin/actions/workflows/default.yml/badge.svg)](https://github.com/casbin/casbin/actions/workflows/default.yml)\n[![Coverage Status](https://coveralls.io/repos/github/casbin/casbin/badge.svg?branch=master)](https://coveralls.io/github/casbin/casbin?branch=master)\n[![Godoc](https://godoc.org/github.com/casbin/casbin?status.svg)](https://pkg.go.dev/github.com/casbin/casbin/v2)\n[![Release](https://img.shields.io/github/release/casbin/casbin.svg)](https://github.com/casbin/casbin/releases/latest)\n[![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN)\n[![Sourcegraph](https://sourcegraph.com/github.com/casbin/casbin/-/badge.svg)](https://sourcegraph.com/github.com/casbin/casbin?badge)\n\n**News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: https://casbin.org/editor/\n\n![casbin Logo](casbin-logo.png)\n\nCasbin 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).\n\n## All the languages supported by Casbin:\n\n| [![golang](https://casbin.org/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/img/langs/php.png)](https://github.com/php-casbin/php-casbin) |\n|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|\n| [Casbin](https://github.com/casbin/casbin)                                             | [jCasbin](https://github.com/casbin/jcasbin)                                        | [node-Casbin](https://github.com/casbin/node-casbin)                                        | [PHP-Casbin](https://github.com/php-casbin/php-casbin)                                   |\n| production-ready                                                                       | production-ready                                                                    | production-ready                                                                            | production-ready                                                                         |\n\n| [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin-net/Casbin.NET) | [![c++](https://casbin.org/img/langs/cpp.png)](https://github.com/casbin/casbin-cpp) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) |\n|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|\n| [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)                                      |\n| production-ready                                                                         | production-ready                                                                               | production-ready                                                                     | production-ready                                                                      |\n\n## Table of contents\n\n- [Supported models](#supported-models)\n- [How it works?](#how-it-works)\n- [Features](#features)\n- [Installation](#installation)\n- [Documentation](#documentation)\n- [Online editor](#online-editor)\n- [Tutorials](#tutorials)\n- [Get started](#get-started)\n- [Policy management](#policy-management)\n- [Policy persistence](#policy-persistence)\n- [Policy consistence between multiple nodes](#policy-consistence-between-multiple-nodes)\n- [Role manager](#role-manager)\n- [Benchmarks](#benchmarks)\n- [Examples](#examples)\n- [Middlewares](#middlewares)\n- [Our adopters](#our-adopters)\n\n## Supported models\n\n1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list)\n2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)**\n3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins.\n3. **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.\n4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)**\n5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time.\n6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants.\n7. **[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.\n8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``.\n9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow.\n10. **Priority**: the policy rules can be prioritized like firewall rules.\n\n## How it works?\n\nIn 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.\n\nThe most basic and simplest model in Casbin is ACL. ACL's model CONF is:\n\n```ini\n# Request definition\n[request_definition]\nr = sub, obj, act\n\n# Policy definition\n[policy_definition]\np = sub, obj, act\n\n# Policy effect\n[policy_effect]\ne = some(where (p.eft == allow))\n\n# Matchers\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act\n\n```\n\nAn example policy for ACL model is like:\n\n```\np, alice, data1, read\np, bob, data2, write\n```\n\nIt means:\n\n- alice can read data1\n- bob can write data2\n\nWe also support multi-line mode by appending '\\\\'  in the end:\n\n```ini\n# Matchers\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj \\\n  && r.act == p.act\n```\n\nFurther 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):\n\n```ini\n# Matchers\n[matchers]\nm = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')\n```\n\nBut you **SHOULD** make sure that the length of the array is **MORE** than **1**, otherwise there will cause it to panic.\n\nFor more operators, you may take a look at [govaluate](https://github.com/casbin/govaluate)\n\n## Features\n\nWhat Casbin does:\n\n1. enforce the policy in the classic ``{subject, object, action}`` form or a customized form as you defined, both allow and deny authorizations are supported.\n2. handle the storage of the access control model and its policy.\n3. manage the role-user mappings and role-role mappings (aka role hierarchy in RBAC).\n4. support built-in superuser like ``root`` or ``administrator``. A superuser can do anything without explicit permissions.\n5. multiple built-in operators to support the rule matching. For example, ``keyMatch`` can map a resource key ``/foo/bar`` to the pattern ``/foo*``.\n\nWhat Casbin does NOT do:\n\n1. authentication (aka verify ``username`` and ``password`` when a user logs in)\n2. manage the list of users or roles. I believe it's more convenient for the project itself to manage these entities. Users usually have their passwords, and Casbin is not designed as a password container. However, Casbin stores the user-role mapping for the RBAC scenario.\n\n## Installation\n\n```\ngo get github.com/casbin/casbin/v3\n```\n\n## Documentation\n\nhttps://casbin.org/docs/overview\n\n## Online editor\n\nYou can also use the online editor (https://casbin.org/editor/) to write your Casbin model and policy in your web browser. It provides functionality such as ``syntax highlighting`` and ``code completion``, just like an IDE for a programming language.\n\n## Tutorials\n\nhttps://casbin.org/docs/tutorials\n\n## Get started\n\n1. New a Casbin enforcer with a model file and a policy file:\n\n    ```go\n    e, _ := casbin.NewEnforcer(\"path/to/model.conf\", \"path/to/policy.csv\")\n    ```\n\nNote: you can also initialize an enforcer with policy in DB instead of file, see [Policy-persistence](#policy-persistence) section for details.\n\n2. Add an enforcement hook into your code right before the access happens:\n\n    ```go\n    sub := \"alice\" // the user that wants to access a resource.\n    obj := \"data1\" // the resource that is going to be accessed.\n    act := \"read\" // the operation that the user performs on the resource.\n\n    if res, _ := e.Enforce(sub, obj, act); res {\n        // permit alice to read data1\n    } else {\n        // deny the request, show an error\n    }\n    ```\n\n3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below:\n\n    ```go\n    roles, _ := e.GetImplicitRolesForUser(sub)\n    ```\n\nSee [Policy management APIs](#policy-management) for more usage.\n\n## Policy management\n\nCasbin provides two sets of APIs to manage permissions:\n\n- [Management API](https://casbin.org/docs/management-api): the primitive API that provides full support for Casbin policy management.\n- [RBAC API](https://casbin.org/docs/rbac-api): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code.\n\nWe also provide a [web-based UI](https://casbin.org/docs/admin-portal) for model management and policy management:\n\n![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png)\n\n![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png)\n\n## Policy persistence\n\nhttps://casbin.org/docs/adapters\n\n## Policy consistence between multiple nodes\n\nhttps://casbin.org/docs/watchers\n\n## Role manager\n\nhttps://casbin.org/docs/role-managers\n\n## Benchmarks\n\nhttps://casbin.org/docs/benchmark\n\n## Examples\n\n| Model                     | Model file                                                                                                                       | Policy file                                                                                                                      |\n|---------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|\n| ACL                       | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf)                                       | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv)                                       |\n| ACL with superuser        | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf)                   | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv)                                       |\n| ACL without users         | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf)           | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv)           |\n| ACL without resources     | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf)   | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv)   |\n| RBAC                      | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf)                                         | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv)                                         |\n| RBAC with resource roles  | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) |\n| RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf)               | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv)               |\n| ABAC                      | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf)                                         | N/A                                                                                                                              |\n| RESTful                   | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf)                                 | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv)                                 |\n| Deny-override             | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf)                     | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv)                     |\n| Priority                  | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf)                                 | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv)                                 |\n\n## Middlewares\n\nAuthz middlewares for web frameworks: https://casbin.org/docs/middlewares\n\n## Our adopters\n\nhttps://casbin.org/docs/adopters\n\n## How to Contribute\n\nPlease read the [contributing guide](CONTRIBUTING.md).\n\n## Contributors\n\nThis project exists thanks to all the people who contribute.\n<a href=\"https://github.com/casbin/casbin/graphs/contributors\"><img src=\"https://opencollective.com/casbin/contributors.svg?width=890&button=false\" /></a>\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=casbin/casbin&type=Date)](https://star-history.com/#casbin/casbin&Date)\n\n## License\n\nThis project is licensed under the [Apache 2.0 license](LICENSE).\n\n## Contact\n\nIf you have any issues or feature requests, please contact us. PR is welcomed.\n- https://github.com/casbin/casbin/issues\n- https://discord.gg/S5UjpzGZjN\n"
  },
  {
    "path": "abac_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\ntype testResource struct {\n\tName  string\n\tOwner string\n}\n\nfunc newTestResource(name string, owner string) testResource {\n\tr := testResource{}\n\tr.Name = name\n\tr.Owner = owner\n\treturn r\n}\n\nfunc TestABACModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_model.conf\")\n\n\tdata1 := newTestResource(\"data1\", \"alice\")\n\tdata2 := newTestResource(\"data2\", \"bob\")\n\n\ttestEnforce(t, e, \"alice\", data1, \"read\", true)\n\ttestEnforce(t, e, \"alice\", data1, \"write\", true)\n\ttestEnforce(t, e, \"alice\", data2, \"read\", false)\n\ttestEnforce(t, e, \"alice\", data2, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data1, \"read\", false)\n\ttestEnforce(t, e, \"bob\", data1, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data2, \"read\", true)\n\ttestEnforce(t, e, \"bob\", data2, \"write\", true)\n}\n\nfunc TestABACMapRequest(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_model.conf\")\n\n\tdata1 := map[string]interface{}{\n\t\t\"Name\":  \"data1\",\n\t\t\"Owner\": \"alice\",\n\t}\n\tdata2 := map[string]interface{}{\n\t\t\"Name\":  \"data2\",\n\t\t\"Owner\": \"bob\",\n\t}\n\n\ttestEnforce(t, e, \"alice\", data1, \"read\", true)\n\ttestEnforce(t, e, \"alice\", data1, \"write\", true)\n\ttestEnforce(t, e, \"alice\", data2, \"read\", false)\n\ttestEnforce(t, e, \"alice\", data2, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data1, \"read\", false)\n\ttestEnforce(t, e, \"bob\", data1, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data2, \"read\", true)\n\ttestEnforce(t, e, \"bob\", data2, \"write\", true)\n}\n\nfunc TestABACTypes(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_model.conf\")\n\tmatcher := `\"moderator\" IN r.sub.Roles && r.sub.Enabled == true && r.sub.Age >= 21 && r.sub.Name != \"foo\"`\n\te.GetModel()[\"m\"][\"m\"].Value = util.RemoveComments(util.EscapeAssertion(matcher))\n\n\tstructRequest := struct {\n\t\tRoles   []interface{}\n\t\tEnabled bool\n\t\tAge     int\n\t\tName    string\n\t}{\n\t\tRoles:   []interface{}{\"user\", \"moderator\"},\n\t\tEnabled: true,\n\t\tAge:     30,\n\t\tName:    \"alice\",\n\t}\n\ttestEnforce(t, e, structRequest, \"\", \"\", true)\n\n\tmapRequest := map[string]interface{}{\n\t\t\"Roles\":   []interface{}{\"user\", \"moderator\"},\n\t\t\"Enabled\": true,\n\t\t\"Age\":     30,\n\t\t\"Name\":    \"alice\",\n\t}\n\ttestEnforce(t, e, mapRequest, nil, \"\", true)\n\n\te.EnableAcceptJsonRequest(true)\n\tjsonRequest, _ := json.Marshal(mapRequest)\n\ttestEnforce(t, e, string(jsonRequest), \"\", \"\", true)\n}\n\nfunc TestABACJsonRequest(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_model.conf\")\n\te.EnableAcceptJsonRequest(true)\n\n\tdata1Json := `{ \"Name\": \"data1\", \"Owner\": \"alice\"}`\n\tdata2Json := `{ \"Name\": \"data2\", \"Owner\": \"bob\"}`\n\n\ttestEnforce(t, e, \"alice\", data1Json, \"read\", true)\n\ttestEnforce(t, e, \"alice\", data1Json, \"write\", true)\n\ttestEnforce(t, e, \"alice\", data2Json, \"read\", false)\n\ttestEnforce(t, e, \"alice\", data2Json, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data1Json, \"read\", false)\n\ttestEnforce(t, e, \"bob\", data1Json, \"write\", false)\n\ttestEnforce(t, e, \"bob\", data2Json, \"read\", true)\n\ttestEnforce(t, e, \"bob\", data2Json, \"write\", true)\n\n\te, _ = NewEnforcer(\"examples/abac_not_using_policy_model.conf\", \"examples/abac_rule_effect_policy.csv\")\n\te.EnableAcceptJsonRequest(true)\n\n\ttestEnforce(t, e, \"alice\", data1Json, \"read\", true)\n\ttestEnforce(t, e, \"alice\", data1Json, \"write\", true)\n\ttestEnforce(t, e, \"alice\", data2Json, \"read\", false)\n\ttestEnforce(t, e, \"alice\", data2Json, \"write\", false)\n\n\te, _ = NewEnforcer(\"examples/abac_rule_model.conf\", \"examples/abac_rule_policy.csv\")\n\te.EnableAcceptJsonRequest(true)\n\tsub1Json := `{\"Name\": \"alice\", \"Age\": 16}`\n\tsub2Json := `{\"Name\": \"alice\", \"Age\": 20}`\n\tsub3Json := `{\"Name\": \"alice\", \"Age\": 65}`\n\n\ttestEnforce(t, e, sub1Json, \"/data1\", \"read\", false)\n\ttestEnforce(t, e, sub1Json, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub1Json, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub1Json, \"/data2\", \"write\", true)\n\ttestEnforce(t, e, sub2Json, \"/data1\", \"read\", true)\n\ttestEnforce(t, e, sub2Json, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub2Json, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub2Json, \"/data2\", \"write\", true)\n\ttestEnforce(t, e, sub3Json, \"/data1\", \"read\", true)\n\ttestEnforce(t, e, sub3Json, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub3Json, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub3Json, \"/data2\", \"write\", false)\n}\n\ntype testSub struct {\n\tName string\n\tAge  int\n}\n\nfunc newTestSubject(name string, age int) testSub {\n\ts := testSub{}\n\ts.Name = name\n\ts.Age = age\n\treturn s\n}\n\nfunc TestABACNotUsingPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_not_using_policy_model.conf\", \"examples/abac_rule_effect_policy.csv\")\n\tdata1 := newTestResource(\"data1\", \"alice\")\n\tdata2 := newTestResource(\"data2\", \"bob\")\n\n\ttestEnforce(t, e, \"alice\", data1, \"read\", true)\n\ttestEnforce(t, e, \"alice\", data1, \"write\", true)\n\ttestEnforce(t, e, \"alice\", data2, \"read\", false)\n\ttestEnforce(t, e, \"alice\", data2, \"write\", false)\n}\n\nfunc TestABACPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/abac_rule_model.conf\", \"examples/abac_rule_policy.csv\")\n\tm := e.GetModel()\n\tfor sec, ast := range m {\n\t\tfmt.Println(sec)\n\t\tfor ptype, p := range ast {\n\t\t\tfmt.Println(ptype, p)\n\t\t}\n\t}\n\tsub1 := newTestSubject(\"alice\", 16)\n\tsub2 := newTestSubject(\"alice\", 20)\n\tsub3 := newTestSubject(\"alice\", 65)\n\n\ttestEnforce(t, e, sub1, \"/data1\", \"read\", false)\n\ttestEnforce(t, e, sub1, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub1, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub1, \"/data2\", \"write\", true)\n\ttestEnforce(t, e, sub2, \"/data1\", \"read\", true)\n\ttestEnforce(t, e, sub2, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub2, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub2, \"/data2\", \"write\", true)\n\ttestEnforce(t, e, sub3, \"/data1\", \"read\", true)\n\ttestEnforce(t, e, sub3, \"/data2\", \"read\", false)\n\ttestEnforce(t, e, sub3, \"/data1\", \"write\", false)\n\ttestEnforce(t, e, sub3, \"/data2\", \"write\", false)\n}\n"
  },
  {
    "path": "ai_api.go",
    "content": "// Copyright 2026 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// AIConfig contains configuration for AI API calls.\ntype AIConfig struct {\n\t// Endpoint is the API endpoint (e.g., \"https://api.openai.com/v1/chat/completions\")\n\tEndpoint string\n\t// APIKey is the authentication key for the API\n\tAPIKey string\n\t// Model is the model to use (e.g., \"gpt-3.5-turbo\", \"gpt-4\")\n\tModel string\n\t// Timeout for API requests (default: 30s)\n\tTimeout time.Duration\n}\n\n// aiMessage represents a message in the OpenAI chat format.\ntype aiMessage struct {\n\tRole    string `json:\"role\"`\n\tContent string `json:\"content\"`\n}\n\n// aiChatRequest represents the request to OpenAI chat completions API.\ntype aiChatRequest struct {\n\tModel    string      `json:\"model\"`\n\tMessages []aiMessage `json:\"messages\"`\n}\n\n// aiChatResponse represents the response from OpenAI chat completions API.\ntype aiChatResponse struct {\n\tChoices []struct {\n\t\tMessage aiMessage `json:\"message\"`\n\t} `json:\"choices\"`\n\tError *struct {\n\t\tMessage string `json:\"message\"`\n\t} `json:\"error,omitempty\"`\n}\n\n// SetAIConfig sets the configuration for AI API calls.\nfunc (e *Enforcer) SetAIConfig(config AIConfig) {\n\tif config.Timeout == 0 {\n\t\tconfig.Timeout = 30 * time.Second\n\t}\n\te.aiConfig = config\n}\n\n// Explain returns an AI-generated explanation of why Enforce returned a particular result.\n// It calls the configured OpenAI-compatible API to generate a natural language explanation.\nfunc (e *Enforcer) Explain(rvals ...interface{}) (string, error) {\n\tif e.aiConfig.Endpoint == \"\" {\n\t\treturn \"\", errors.New(\"AI config not set, use SetAIConfig first\")\n\t}\n\n\t// Get enforcement result and matched rules\n\tresult, matchedRules, err := e.EnforceEx(rvals...)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to enforce: %w\", err)\n\t}\n\n\t// Build context for AI\n\texplainContext := e.buildExplainContext(rvals, result, matchedRules)\n\n\t// Call AI API\n\texplanation, err := e.callAIAPI(explainContext)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get AI explanation: %w\", err)\n\t}\n\n\treturn explanation, nil\n}\n\n// buildExplainContext builds the context string for AI explanation.\nfunc (e *Enforcer) buildExplainContext(rvals []interface{}, result bool, matchedRules []string) string {\n\tvar sb strings.Builder\n\n\t// Add request information\n\tsb.WriteString(\"Authorization Request:\\n\")\n\tsb.WriteString(fmt.Sprintf(\"Subject: %v\\n\", rvals[0]))\n\tif len(rvals) > 1 {\n\t\tsb.WriteString(fmt.Sprintf(\"Object: %v\\n\", rvals[1]))\n\t}\n\tif len(rvals) > 2 {\n\t\tsb.WriteString(fmt.Sprintf(\"Action: %v\\n\", rvals[2]))\n\t}\n\tsb.WriteString(fmt.Sprintf(\"\\nEnforcement Result: %v\\n\", result))\n\n\t// Add matched rules\n\tif len(matchedRules) > 0 {\n\t\tsb.WriteString(\"\\nMatched Policy Rules:\\n\")\n\t\tfor _, rule := range matchedRules {\n\t\t\tsb.WriteString(fmt.Sprintf(\"- %s\\n\", rule))\n\t\t}\n\t} else {\n\t\tsb.WriteString(\"\\nNo policy rules matched.\\n\")\n\t}\n\n\t// Add model information\n\tsb.WriteString(\"\\nAccess Control Model:\\n\")\n\tif m, ok := e.model[\"m\"]; ok {\n\t\tfor key, ast := range m {\n\t\t\tsb.WriteString(fmt.Sprintf(\"Matcher (%s): %s\\n\", key, ast.Value))\n\t\t}\n\t}\n\tif eff, ok := e.model[\"e\"]; ok {\n\t\tfor key, ast := range eff {\n\t\t\tsb.WriteString(fmt.Sprintf(\"Effect (%s): %s\\n\", key, ast.Value))\n\t\t}\n\t}\n\n\t// Add all policies\n\tpolicies, _ := e.GetPolicy()\n\tif len(policies) > 0 {\n\t\tsb.WriteString(\"\\nAll Policy Rules:\\n\")\n\t\tfor _, policy := range policies {\n\t\t\tsb.WriteString(fmt.Sprintf(\"- %s\\n\", strings.Join(policy, \", \")))\n\t\t}\n\t}\n\n\treturn sb.String()\n}\n\n// callAIAPI calls the configured AI API to get an explanation.\nfunc (e *Enforcer) callAIAPI(explainContext string) (string, error) {\n\t// Prepare the request\n\tmessages := []aiMessage{\n\t\t{\n\t\t\tRole: \"system\",\n\t\t\tContent: \"You are an expert in access control and authorization systems. \" +\n\t\t\t\t\"Explain why an authorization request was allowed or denied based on the \" +\n\t\t\t\t\"provided access control model, policies, and enforcement result. \" +\n\t\t\t\t\"Be clear, concise, and educational.\",\n\t\t},\n\t\t{\n\t\t\tRole:    \"user\",\n\t\t\tContent: fmt.Sprintf(\"Please explain the following authorization decision:\\n\\n%s\", explainContext),\n\t\t},\n\t}\n\n\treqBody := aiChatRequest{\n\t\tModel:    e.aiConfig.Model,\n\t\tMessages: messages,\n\t}\n\n\tjsonData, err := json.Marshal(reqBody)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal request: %w\", err)\n\t}\n\n\t// Create HTTP request with context\n\treqCtx, cancel := context.WithTimeout(context.Background(), e.aiConfig.Timeout)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(reqCtx, http.MethodPost, e.aiConfig.Endpoint, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+e.aiConfig.APIKey)\n\n\t// Execute request\n\tclient := &http.Client{}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to execute request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// Read response\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read response: %w\", err)\n\t}\n\n\t// Parse response\n\tvar chatResp aiChatResponse\n\tif err := json.Unmarshal(body, &chatResp); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse response: %w\", err)\n\t}\n\n\t// Check for API errors\n\tif chatResp.Error != nil {\n\t\treturn \"\", fmt.Errorf(\"API error: %s\", chatResp.Error.Message)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"API returned status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\t// Extract explanation\n\tif len(chatResp.Choices) == 0 {\n\t\treturn \"\", errors.New(\"no response from AI\")\n\t}\n\n\treturn chatResp.Choices[0].Message.Content, nil\n}\n"
  },
  {
    "path": "ai_api_test.go",
    "content": "// Copyright 2026 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\n// TestExplainWithoutConfig tests that Explain returns error when config is not set.\nfunc TestExplainWithoutConfig(t *testing.T) {\n\te, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = e.Explain(\"alice\", \"data1\", \"read\")\n\tif err == nil {\n\t\tt.Error(\"Expected error when AI config is not set\")\n\t}\n\tif !strings.Contains(err.Error(), \"AI config not set\") {\n\t\tt.Errorf(\"Expected 'AI config not set' error, got: %v\", err)\n\t}\n}\n\n// TestExplainWithMockAPI tests Explain with a mock OpenAI-compatible API.\nfunc TestExplainWithMockAPI(t *testing.T) {\n\t// Create a mock server that simulates OpenAI API\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// Verify request\n\t\tif r.Method != http.MethodPost {\n\t\t\tt.Errorf(\"Expected POST request, got %s\", r.Method)\n\t\t}\n\t\tif r.Header.Get(\"Content-Type\") != \"application/json\" {\n\t\t\tt.Errorf(\"Expected Content-Type: application/json, got %s\", r.Header.Get(\"Content-Type\"))\n\t\t}\n\t\tif !strings.HasPrefix(r.Header.Get(\"Authorization\"), \"Bearer \") {\n\t\t\tt.Errorf(\"Expected Bearer token in Authorization header, got %s\", r.Header.Get(\"Authorization\"))\n\t\t}\n\n\t\t// Parse request to verify structure\n\t\tvar req aiChatRequest\n\t\tif err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n\t\t\tt.Errorf(\"Failed to decode request: %v\", err)\n\t\t}\n\n\t\tif req.Model != \"gpt-3.5-turbo\" {\n\t\t\tt.Errorf(\"Expected model gpt-3.5-turbo, got %s\", req.Model)\n\t\t}\n\n\t\tif len(req.Messages) != 2 {\n\t\t\tt.Errorf(\"Expected 2 messages, got %d\", len(req.Messages))\n\t\t}\n\n\t\t// Send mock response\n\t\tresp := aiChatResponse{\n\t\t\tChoices: []struct {\n\t\t\t\tMessage aiMessage `json:\"message\"`\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tMessage: aiMessage{\n\t\t\t\t\t\tRole:    \"assistant\",\n\t\t\t\t\t\tContent: \"The request was allowed because alice has read permission on data1 according to the policy rule.\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\t\tjson.NewEncoder(w).Encode(resp)\n\t}))\n\tdefer mockServer.Close()\n\n\t// Create enforcer\n\te, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Set AI config with mock server\n\te.SetAIConfig(AIConfig{\n\t\tEndpoint: mockServer.URL,\n\t\tAPIKey:   \"test-api-key\",\n\t\tModel:    \"gpt-3.5-turbo\",\n\t\tTimeout:  5 * time.Second,\n\t})\n\n\t// Test explanation for allowed request\n\texplanation, err := e.Explain(\"alice\", \"data1\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to get explanation: %v\", err)\n\t}\n\n\tif explanation == \"\" {\n\t\tt.Error(\"Expected non-empty explanation\")\n\t}\n\n\tif !strings.Contains(explanation, \"allowed\") {\n\t\tt.Errorf(\"Expected explanation to mention 'allowed', got: %s\", explanation)\n\t}\n}\n\n// TestExplainDenied tests Explain for a denied request.\nfunc TestExplainDenied(t *testing.T) {\n\t// Create a mock server\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresp := aiChatResponse{\n\t\t\tChoices: []struct {\n\t\t\t\tMessage aiMessage `json:\"message\"`\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tMessage: aiMessage{\n\t\t\t\t\t\tRole:    \"assistant\",\n\t\t\t\t\t\tContent: \"The request was denied because there is no policy rule that allows alice to write to data1.\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusOK)\n\t\tjson.NewEncoder(w).Encode(resp)\n\t}))\n\tdefer mockServer.Close()\n\n\t// Create enforcer\n\te, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Set AI config\n\te.SetAIConfig(AIConfig{\n\t\tEndpoint: mockServer.URL,\n\t\tAPIKey:   \"test-api-key\",\n\t\tModel:    \"gpt-3.5-turbo\",\n\t\tTimeout:  5 * time.Second,\n\t})\n\n\t// Test explanation for denied request\n\texplanation, err := e.Explain(\"alice\", \"data1\", \"write\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to get explanation: %v\", err)\n\t}\n\n\tif explanation == \"\" {\n\t\tt.Error(\"Expected non-empty explanation\")\n\t}\n\n\tif !strings.Contains(explanation, \"denied\") {\n\t\tt.Errorf(\"Expected explanation to mention 'denied', got: %s\", explanation)\n\t}\n}\n\n// TestExplainAPIError tests handling of API errors.\nfunc TestExplainAPIError(t *testing.T) {\n\t// Create a mock server that returns an error\n\tmockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresp := aiChatResponse{\n\t\t\tError: &struct {\n\t\t\t\tMessage string `json:\"message\"`\n\t\t\t}{\n\t\t\t\tMessage: \"Invalid API key\",\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\tjson.NewEncoder(w).Encode(resp)\n\t}))\n\tdefer mockServer.Close()\n\n\t// Create enforcer\n\te, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Set AI config\n\te.SetAIConfig(AIConfig{\n\t\tEndpoint: mockServer.URL,\n\t\tAPIKey:   \"invalid-key\",\n\t\tModel:    \"gpt-3.5-turbo\",\n\t\tTimeout:  5 * time.Second,\n\t})\n\n\t// Test that API error is properly handled\n\t_, err = e.Explain(\"alice\", \"data1\", \"read\")\n\tif err == nil {\n\t\tt.Error(\"Expected error for API failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"Invalid API key\") {\n\t\tt.Errorf(\"Expected API error message, got: %v\", err)\n\t}\n}\n\n// TestBuildExplainContext tests the context building function.\nfunc TestBuildExplainContext(t *testing.T) {\n\te, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Test with matched rules\n\trvals := []interface{}{\"alice\", \"data1\", \"read\"}\n\tresult := true\n\tmatchedRules := []string{\"alice, data1, read\"}\n\n\tcontext := e.buildExplainContext(rvals, result, matchedRules)\n\n\t// Verify context contains expected elements\n\tif !strings.Contains(context, \"alice\") {\n\t\tt.Error(\"Context should contain subject 'alice'\")\n\t}\n\tif !strings.Contains(context, \"data1\") {\n\t\tt.Error(\"Context should contain object 'data1'\")\n\t}\n\tif !strings.Contains(context, \"read\") {\n\t\tt.Error(\"Context should contain action 'read'\")\n\t}\n\tif !strings.Contains(context, \"true\") {\n\t\tt.Error(\"Context should contain result 'true'\")\n\t}\n\tif !strings.Contains(context, \"alice, data1, read\") {\n\t\tt.Error(\"Context should contain matched rule\")\n\t}\n\n\t// Test with no matched rules\n\tcontext2 := e.buildExplainContext(rvals, false, []string{})\n\tif !strings.Contains(context2, \"No policy rules matched\") {\n\t\tt.Error(\"Context should indicate no matched rules\")\n\t}\n}\n"
  },
  {
    "path": "biba_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\nfunc testEnforceBiba(t *testing.T, e *Enforcer, sub string, subLevel float64, obj string, objLevel float64, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, subLevel, obj, objLevel, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, %v, %s, %v, %s: %t, supposed to be %t\", sub, subLevel, obj, objLevel, act, myRes, res)\n\t}\n}\n\nfunc TestBibaModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/biba_model.conf\")\n\n\ttestEnforceBiba(t, e, \"alice\", 3, \"data1\", 1, \"read\", false)\n\ttestEnforceBiba(t, e, \"bob\", 2, \"data2\", 2, \"read\", true)\n\ttestEnforceBiba(t, e, \"charlie\", 1, \"data1\", 1, \"read\", true)\n\ttestEnforceBiba(t, e, \"bob\", 2, \"data3\", 3, \"read\", true)\n\ttestEnforceBiba(t, e, \"charlie\", 1, \"data2\", 2, \"read\", true)\n\n\ttestEnforceBiba(t, e, \"alice\", 3, \"data3\", 3, \"write\", true)\n\ttestEnforceBiba(t, e, \"bob\", 2, \"data3\", 3, \"write\", false)\n\ttestEnforceBiba(t, e, \"charlie\", 1, \"data2\", 2, \"write\", false)\n\ttestEnforceBiba(t, e, \"alice\", 3, \"data1\", 1, \"write\", true)\n\ttestEnforceBiba(t, e, \"bob\", 2, \"data1\", 1, \"write\", true)\n}\n"
  },
  {
    "path": "blp_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\nfunc testEnforceBLP(t *testing.T, e *Enforcer, sub string, subLevel float64, obj string, objLevel float64, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, subLevel, obj, objLevel, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, %v, %s, %v, %s: %t, supposed to be %t\", sub, subLevel, obj, objLevel, act, myRes, res)\n\t}\n}\n\nfunc TestBLPModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/blp_model.conf\")\n\n\t// Read operations: subject level >= object level\n\ttestEnforceBLP(t, e, \"alice\", 3, \"data1\", 1, \"read\", true)\n\ttestEnforceBLP(t, e, \"bob\", 2, \"data2\", 2, \"read\", true)\n\ttestEnforceBLP(t, e, \"charlie\", 1, \"data1\", 1, \"read\", true)\n\n\t// Read violations: subject level < object level\n\ttestEnforceBLP(t, e, \"bob\", 2, \"data3\", 3, \"read\", false)\n\ttestEnforceBLP(t, e, \"charlie\", 1, \"data2\", 2, \"read\", false)\n\n\t// Write operations: subject level <= object level\n\ttestEnforceBLP(t, e, \"alice\", 3, \"data3\", 3, \"write\", true)\n\ttestEnforceBLP(t, e, \"bob\", 2, \"data3\", 3, \"write\", true)\n\ttestEnforceBLP(t, e, \"charlie\", 1, \"data2\", 2, \"write\", true)\n\n\t// Write violations: subject level > object level\n\ttestEnforceBLP(t, e, \"alice\", 3, \"data1\", 1, \"write\", false)\n\ttestEnforceBLP(t, e, \"bob\", 2, \"data1\", 1, \"write\", false)\n}\n"
  },
  {
    "path": "config/config.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage config\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\t// DEFAULT_SECTION specifies the name of a section if no name provided.\n\tDEFAULT_SECTION = \"default\"\n\t// DEFAULT_COMMENT defines what character(s) indicate a comment `#`.\n\tDEFAULT_COMMENT = []byte{'#'}\n\t// DEFAULT_COMMENT_SEM defines what alternate character(s) indicate a comment `;`.\n\tDEFAULT_COMMENT_SEM = []byte{';'}\n\t// DEFAULT_MULTI_LINE_SEPARATOR defines what character indicates a multi-line content.\n\tDEFAULT_MULTI_LINE_SEPARATOR = []byte{'\\\\'}\n)\n\n// ConfigInterface defines the behavior of a Config implementation.\ntype ConfigInterface interface {\n\tString(key string) string\n\tStrings(key string) []string\n\tBool(key string) (bool, error)\n\tInt(key string) (int, error)\n\tInt64(key string) (int64, error)\n\tFloat64(key string) (float64, error)\n\tSet(key string, value string) error\n}\n\n// Config represents an implementation of the ConfigInterface.\ntype Config struct {\n\t// Section:key=value\n\tdata map[string]map[string]string\n}\n\n// NewConfig create an empty configuration representation from file.\nfunc NewConfig(confName string) (ConfigInterface, error) {\n\tc := &Config{\n\t\tdata: make(map[string]map[string]string),\n\t}\n\terr := c.parse(confName)\n\treturn c, err\n}\n\n// NewConfigFromText create an empty configuration representation from text.\nfunc NewConfigFromText(text string) (ConfigInterface, error) {\n\tc := &Config{\n\t\tdata: make(map[string]map[string]string),\n\t}\n\terr := c.parseBuffer(bufio.NewReader(strings.NewReader(text)))\n\treturn c, err\n}\n\n// AddConfig adds a new section->key:value to the configuration.\nfunc (c *Config) AddConfig(section string, option string, value string) bool {\n\tif section == \"\" {\n\t\tsection = DEFAULT_SECTION\n\t}\n\n\tif _, ok := c.data[section]; !ok {\n\t\tc.data[section] = make(map[string]string)\n\t}\n\n\t_, ok := c.data[section][option]\n\tc.data[section][option] = value\n\n\treturn !ok\n}\n\nfunc (c *Config) parse(fname string) (err error) {\n\tf, err := os.Open(fname)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tbuf := bufio.NewReader(f)\n\treturn c.parseBuffer(buf)\n}\n\nfunc (c *Config) parseBuffer(buf *bufio.Reader) error {\n\tvar section string\n\tvar lineNum int\n\tvar buffer bytes.Buffer\n\tvar canWrite bool\n\tfor {\n\t\tif canWrite {\n\t\t\tif err := c.write(section, lineNum, &buffer); err != nil {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\tcanWrite = false\n\t\t\t}\n\t\t}\n\t\tlineNum++\n\t\tline, _, err := buf.ReadLine()\n\t\tif err == io.EOF {\n\t\t\t// force write when buffer is not flushed yet\n\t\t\tif buffer.Len() > 0 {\n\t\t\t\tif err = c.write(section, lineNum, &buffer); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tline = bytes.TrimSpace(line)\n\t\tswitch {\n\t\tcase bytes.Equal(line, []byte{}), bytes.HasPrefix(line, DEFAULT_COMMENT_SEM),\n\t\t\tbytes.HasPrefix(line, DEFAULT_COMMENT):\n\t\t\tcanWrite = true\n\t\t\tcontinue\n\t\tcase bytes.HasPrefix(line, []byte{'['}) && bytes.HasSuffix(line, []byte{']'}):\n\t\t\t// force write when buffer is not flushed yet\n\t\t\tif buffer.Len() > 0 {\n\t\t\t\tif err := c.write(section, lineNum, &buffer); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcanWrite = false\n\t\t\t}\n\t\t\tsection = string(line[1 : len(line)-1])\n\t\tdefault:\n\t\t\tvar p []byte\n\t\t\tif bytes.HasSuffix(line, DEFAULT_MULTI_LINE_SEPARATOR) {\n\t\t\t\tp = bytes.TrimSpace(line[:len(line)-1])\n\t\t\t\tp = append(p, \" \"...)\n\t\t\t} else {\n\t\t\t\tp = line\n\t\t\t\tcanWrite = true\n\t\t\t}\n\n\t\t\tend := len(p)\n\t\t\tfor i, value := range p {\n\t\t\t\tif value == DEFAULT_COMMENT[0] || value == DEFAULT_COMMENT_SEM[0] {\n\t\t\t\t\tend = i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif _, err := buffer.Write(p[:end]); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) write(section string, lineNum int, b *bytes.Buffer) error {\n\tif b.Len() <= 0 {\n\t\treturn nil\n\t}\n\n\toptionVal := bytes.SplitN(b.Bytes(), []byte{'='}, 2)\n\tif len(optionVal) != 2 {\n\t\treturn fmt.Errorf(\"parse the content error : line %d , %s = ? \", lineNum, optionVal[0])\n\t}\n\toption := bytes.TrimSpace(optionVal[0])\n\tvalue := bytes.TrimSpace(optionVal[1])\n\tc.AddConfig(section, string(option), string(value))\n\n\t// flush buffer after adding\n\tb.Reset()\n\n\treturn nil\n}\n\n// Bool lookups up the value using the provided key and converts the value to a bool.\nfunc (c *Config) Bool(key string) (bool, error) {\n\treturn strconv.ParseBool(c.get(key))\n}\n\n// Int lookups up the value using the provided key and converts the value to a int.\nfunc (c *Config) Int(key string) (int, error) {\n\treturn strconv.Atoi(c.get(key))\n}\n\n// Int64 lookups up the value using the provided key and converts the value to a int64.\nfunc (c *Config) Int64(key string) (int64, error) {\n\treturn strconv.ParseInt(c.get(key), 10, 64)\n}\n\n// Float64 lookups up the value using the provided key and converts the value to a float64.\nfunc (c *Config) Float64(key string) (float64, error) {\n\treturn strconv.ParseFloat(c.get(key), 64)\n}\n\n// String lookups up the value using the provided key and converts the value to a string.\nfunc (c *Config) String(key string) string {\n\treturn c.get(key)\n}\n\n// Strings lookups up the value using the provided key and converts the value to an array of string\n// by splitting the string by comma.\nfunc (c *Config) Strings(key string) []string {\n\tv := c.get(key)\n\tif v == \"\" {\n\t\treturn nil\n\t}\n\treturn strings.Split(v, \",\")\n}\n\n// Set sets the value for the specific key in the Config.\nfunc (c *Config) Set(key string, value string) error {\n\tif len(key) == 0 {\n\t\treturn errors.New(\"key is empty\")\n\t}\n\n\tvar (\n\t\tsection string\n\t\toption  string\n\t)\n\n\tkeys := strings.Split(strings.ToLower(key), \"::\")\n\tif len(keys) >= 2 {\n\t\tsection = keys[0]\n\t\toption = keys[1]\n\t} else {\n\t\toption = keys[0]\n\t}\n\n\tc.AddConfig(section, option, value)\n\treturn nil\n}\n\n// section.key or key.\nfunc (c *Config) get(key string) string {\n\tvar (\n\t\tsection string\n\t\toption  string\n\t)\n\n\tkeys := strings.Split(strings.ToLower(key), \"::\")\n\tif len(keys) >= 2 {\n\t\tsection = keys[0]\n\t\toption = keys[1]\n\t} else {\n\t\tsection = DEFAULT_SECTION\n\t\toption = keys[0]\n\t}\n\n\tif value, ok := c.data[section][option]; ok {\n\t\treturn value\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "config/config_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage config\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGet(t *testing.T) {\n\tconfig, cerr := NewConfig(\"testdata/testini.ini\")\n\tif cerr != nil {\n\t\tt.Errorf(\"Configuration file loading failed, err:%v\", cerr.Error())\n\t\tt.Fatalf(\"err: %v\", cerr)\n\t}\n\n\t// default::key test\n\tif v, err := config.Bool(\"debug\"); err != nil || !v {\n\t\tt.Errorf(\"Get failure: expected different value for debug (expected: [%#v] got: [%#v])\", true, v)\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tif v := config.String(\"url\"); v != \"act.wiki\" {\n\t\tt.Errorf(\"Get failure: expected different value for url (expected: [%#v] got: [%#v])\", \"act.wiki\", v)\n\t}\n\n\t// redis::key test\n\tif v := config.Strings(\"redis::redis.key\"); len(v) != 2 || v[0] != \"push1\" || v[1] != \"push2\" {\n\t\tt.Errorf(\"Get failure: expected different value for redis::redis.key (expected: [%#v] got: [%#v])\", \"[]string{push1,push2}\", v)\n\t}\n\tif v := config.String(\"mysql::mysql.dev.host\"); v != \"127.0.0.1\" {\n\t\tt.Errorf(\"Get failure: expected different value for mysql::mysql.dev.host (expected: [%#v] got: [%#v])\", \"127.0.0.1\", v)\n\t}\n\tif v := config.String(\"mysql::mysql.master.host\"); v != \"10.0.0.1\" {\n\t\tt.Errorf(\"Get failure: expected different value for mysql::mysql.master.host (expected: [%#v] got: [%#v])\", \"10.0.0.1\", v)\n\t}\n\tif v := config.String(\"mysql::mysql.master.user\"); v != \"root\" {\n\t\tt.Errorf(\"Get failure: expected different value for mysql::mysql.master.user (expected: [%#v] got: [%#v])\", \"root\", v)\n\t}\n\tif v := config.String(\"mysql::mysql.master.pass\"); v != \"89dds)2$\" {\n\t\tt.Errorf(\"Get failure: expected different value for mysql::mysql.master.pass (expected: [%#v] got: [%#v])\", \"89dds)2$\", v)\n\t}\n\t// math::key test\n\tif v, err := config.Int64(\"math::math.i64\"); err != nil || v != 64 {\n\t\tt.Errorf(\"Get failure: expected different value for math::math.i64 (expected: [%#v] got: [%#v])\", 64, v)\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tif v, err := config.Float64(\"math::math.f64\"); err != nil || v != 64.1 {\n\t\tt.Errorf(\"Get failure: expected different value for math::math.f64 (expected: [%#v] got: [%#v])\", 64.1, v)\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\n\t_ = config.Set(\"other::key1\", \"new test key\")\n\n\tif v := config.String(\"other::key1\"); v != \"new test key\" {\n\t\tt.Errorf(\"Get failure: expected different value for other::key1 (expected: [%#v] got: [%#v])\", \"new test key\", v)\n\t}\n\n\t_ = config.Set(\"other::key1\", \"test key\")\n\n\tif v := config.String(\"multi1::name\"); v != \"r.sub==p.sub && r.obj==p.obj\" {\n\t\tt.Errorf(\"Get failure: expected different value for multi1::name (expected: [%#v] got: [%#v])\", \"r.sub==p.sub&&r.obj==p.obj\", v)\n\t}\n\n\tif v := config.String(\"multi2::name\"); v != \"r.sub==p.sub && r.obj==p.obj\" {\n\t\tt.Errorf(\"Get failure: expected different value for multi2::name (expected: [%#v] got: [%#v])\", \"r.sub==p.sub&&r.obj==p.obj\", v)\n\t}\n\n\tif v := config.String(\"multi3::name\"); v != \"r.sub==p.sub && r.obj==p.obj\" {\n\t\tt.Errorf(\"Get failure: expected different value for multi3::name (expected: [%#v] got: [%#v])\", \"r.sub==p.sub&&r.obj==p.obj\", v)\n\t}\n\n\tif v := config.String(\"multi4::name\"); v != \"\" {\n\t\tt.Errorf(\"Get failure: expected different value for multi4::name (expected: [%#v] got: [%#v])\", \"\", v)\n\t}\n\n\tif v := config.String(\"multi5::name\"); v != \"r.sub==p.sub && r.obj==p.obj\" {\n\t\tt.Errorf(\"Get failure: expected different value for multi5::name (expected: [%#v] got: [%#v])\", \"r.sub==p.sub&&r.obj==p.obj\", v)\n\t}\n}\n"
  },
  {
    "path": "config/testdata/testini.ini",
    "content": "# test config\ndebug = true\nurl = act.wiki\n\n; redis config\n[redis]\nredis.key = push1,push2\n\n; mysql config\n[mysql]\nmysql.dev.host = 127.0.0.1\nmysql.dev.user = root\nmysql.dev.pass = 123456\nmysql.dev.db = test\n\nmysql.master.host = 10.0.0.1 # host # 10.0.0.1\nmysql.master.user = root ; user name\nmysql.master.pass = 89dds)2$#d\nmysql.master.db = act\n\n; math config\n[math]\nmath.i64 = 64\nmath.f64 = 64.1\n\n# multi-line test\n[multi1]\nname = r.sub==p.sub \\\n   && r.obj==p.obj\\\n   \\\n[multi2]\nname = r.sub==p.sub \\\n   && r.obj==p.obj\n\n[multi3]\nname = r.sub==p.sub \\\n   && r.obj==p.obj\n\n[multi4]\nname = \\\n\\\n \\\n\n[multi5]\nname = r.sub==p.sub \\\n   && r.obj==p.obj\\\n   \\\n"
  },
  {
    "path": "constant/constants.go",
    "content": "// Copyright 2022 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage constant\n\nconst (\n\tActionIndex   = \"act\"\n\tDomainIndex   = \"dom\"\n\tSubjectIndex  = \"sub\"\n\tObjectIndex   = \"obj\"\n\tPriorityIndex = \"priority\"\n)\n\nconst (\n\tAllowOverrideEffect   = \"some(where (p_eft == allow))\"\n\tDenyOverrideEffect    = \"!some(where (p_eft == deny))\"\n\tAllowAndDenyEffect    = \"some(where (p_eft == allow)) && !some(where (p_eft == deny))\"\n\tPriorityEffect        = \"priority(p_eft) || deny\"\n\tSubjectPriorityEffect = \"subjectPriority(p_eft) || deny\"\n)\n"
  },
  {
    "path": "constraint_test.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\nfunc TestConstraintSOD(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = sod(\"role1\", \"role2\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Add a user to role1 should succeed\n\t_, err = e.AddRoleForUser(\"alice\", \"role1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add role1 to alice: %v\", err)\n\t}\n\n\t// Add a different user to role2 should succeed\n\t_, err = e.AddRoleForUser(\"bob\", \"role2\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add role2 to bob: %v\", err)\n\t}\n\n\t// Try to add role2 to alice should fail (SOD violation)\n\t_, err = e.AddRoleForUser(\"alice\", \"role2\")\n\tif err == nil {\n\t\tt.Fatal(\"Expected constraint violation error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint violation\") {\n\t\tt.Fatalf(\"Expected constraint violation error, got: %v\", err)\n\t}\n}\n\nfunc TestConstraintSODMax(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = sodMax([\"role1\", \"role2\", \"role3\"], 1)\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Add user to one role should succeed\n\t_, err = e.AddRoleForUser(\"alice\", \"role1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add role1 to alice: %v\", err)\n\t}\n\n\t// Try to add user to another role from the set should fail\n\t_, err = e.AddRoleForUser(\"alice\", \"role2\")\n\tif err == nil {\n\t\tt.Fatal(\"Expected constraint violation error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint violation\") {\n\t\tt.Fatalf(\"Expected constraint violation error, got: %v\", err)\n\t}\n}\n\nfunc TestConstraintRoleMax(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = roleMax(\"admin\", 2)\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Add first user to admin role should succeed\n\t_, err = e.AddRoleForUser(\"alice\", \"admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add admin to alice: %v\", err)\n\t}\n\n\t// Add second user to admin role should succeed\n\t_, err = e.AddRoleForUser(\"bob\", \"admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add admin to bob: %v\", err)\n\t}\n\n\t// Try to add third user to admin role should fail (exceeds max)\n\t_, err = e.AddRoleForUser(\"charlie\", \"admin\")\n\tif err == nil {\n\t\tt.Fatal(\"Expected constraint violation error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint violation\") {\n\t\tt.Fatalf(\"Expected constraint violation error, got: %v\", err)\n\t}\n}\n\nfunc TestConstraintRolePre(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = rolePre(\"db_admin\", \"security_trained\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Try to add db_admin without prerequisite should fail\n\t_, err = e.AddRoleForUser(\"alice\", \"db_admin\")\n\tif err == nil {\n\t\tt.Fatal(\"Expected constraint violation error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint violation\") {\n\t\tt.Fatalf(\"Expected constraint violation error, got: %v\", err)\n\t}\n\n\t// Add prerequisite role first\n\t_, err = e.AddRoleForUser(\"alice\", \"security_trained\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add security_trained to alice: %v\", err)\n\t}\n\n\t// Now adding db_admin should succeed\n\t_, err = e.AddRoleForUser(\"alice\", \"db_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add db_admin to alice: %v\", err)\n\t}\n}\n\nfunc TestConstraintWithoutRBAC(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[constraint_definition]\nc = sod(\"role1\", \"role2\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act\n`\n\n\t_, err := model.NewModelFromString(modelText)\n\tif err == nil {\n\t\tt.Fatal(\"Expected error for constraints without RBAC, got nil\")\n\t}\n\tif err != errors.ErrConstraintRequiresRBAC {\n\t\tt.Fatalf(\"Expected ErrConstraintRequiresRBAC, got: %v\", err)\n\t}\n}\n\nfunc TestConstraintParsingError(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = invalidFunction(\"role1\", \"role2\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\t_, err := model.NewModelFromString(modelText)\n\tif err == nil {\n\t\tt.Fatal(\"Expected parsing error for invalid constraint, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint parsing error\") {\n\t\tt.Fatalf(\"Expected constraint parsing error, got: %v\", err)\n\t}\n}\n\nfunc TestConstraintRollback(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = sod(\"role1\", \"role2\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Add alice to role1\n\t_, err = e.AddRoleForUser(\"alice\", \"role1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add role1 to alice: %v\", err)\n\t}\n\n\t// Try to add alice to role2 (should fail with constraint violation)\n\t_, err = e.AddRoleForUser(\"alice\", \"role2\")\n\tif err == nil {\n\t\tt.Fatal(\"Expected constraint violation error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"constraint violation\") {\n\t\tt.Fatalf(\"Expected constraint violation error, got: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "detector/default_detector.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage detector\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n)\n\n// rangeableRM is an interface for role managers that support iterating over all role links.\n// This is used to build the adjacency graph for cycle detection.\ntype rangeableRM interface {\n\tRange(func(name1, name2 string, domain ...string) bool)\n}\n\n// DefaultDetector is the default implementation of the Detector interface.\n// It uses depth-first search (DFS) to detect cycles in role inheritance.\ntype DefaultDetector struct{}\n\n// NewDefaultDetector creates a new instance of DefaultDetector.\nfunc NewDefaultDetector() *DefaultDetector {\n\treturn &DefaultDetector{}\n}\n\n// Check checks whether the current status of the passed-in RoleManager contains logical errors (e.g., cycles in role inheritance).\n// It uses DFS to traverse the role graph and detect cycles.\n// Returns nil if no cycle is found, otherwise returns an error with a description of the cycle.\nfunc (d *DefaultDetector) Check(rm rbac.RoleManager) error {\n\t// Defensive nil check to prevent runtime panics\n\tif rm == nil {\n\t\treturn fmt.Errorf(\"role manager cannot be nil\")\n\t}\n\n\t// Build the adjacency graph by exploring all roles\n\tgraph, err := d.buildGraph(rm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Run DFS to detect cycles\n\tvisited := make(map[string]bool)\n\trecursionStack := make(map[string]bool)\n\n\tfor role := range graph {\n\t\tif !visited[role] {\n\t\t\tif cycle := d.detectCycle(role, graph, visited, recursionStack, []string{}); cycle != nil {\n\t\t\t\treturn fmt.Errorf(\"cycle detected: %s\", strings.Join(cycle, \" -> \"))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// buildGraph builds an adjacency list representation of the role inheritance graph.\n// It uses the Range method (via type assertion) to iterate through all role links.\nfunc (d *DefaultDetector) buildGraph(rm rbac.RoleManager) (graph map[string][]string, err error) {\n\tgraph = make(map[string][]string)\n\n\t// Recover from any panics during Range iteration (e.g., nil pointer dereferences)\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"RoleManager is not properly initialized: %v\", r)\n\t\t}\n\t}()\n\n\t// Try to cast to a RoleManager implementation that supports Range\n\t// This works with RoleManagerImpl and similar implementations\n\trrm, ok := rm.(rangeableRM)\n\tif !ok {\n\t\t// Return an error if the RoleManager doesn't support Range iteration\n\t\treturn nil, fmt.Errorf(\"RoleManager does not support Range iteration, cannot detect cycles\")\n\t}\n\n\t// Use Range method to build the graph directly\n\trrm.Range(func(name1, name2 string, domain ...string) bool {\n\t\t// Initialize empty slice for name1 if it doesn't exist\n\t\tif graph[name1] == nil {\n\t\t\tgraph[name1] = []string{}\n\t\t}\n\t\t// Add the link: name1 -> name2\n\t\tgraph[name1] = append(graph[name1], name2)\n\n\t\t// Ensure name2 exists in graph even if it has no outgoing edges\n\t\tif graph[name2] == nil {\n\t\t\tgraph[name2] = []string{}\n\t\t}\n\t\treturn true\n\t})\n\treturn graph, nil\n}\n\n// detectCycle performs DFS to detect cycles in the role graph.\n// Returns a slice representing the cycle path if found, nil otherwise.\nfunc (d *DefaultDetector) detectCycle(\n\trole string,\n\tgraph map[string][]string,\n\tvisited map[string]bool,\n\trecursionStack map[string]bool,\n\tpath []string,\n) []string {\n\t// Mark the current role as visited and add to recursion stack\n\tvisited[role] = true\n\trecursionStack[role] = true\n\tpath = append(path, role)\n\n\t// Visit all neighbors (parent roles)\n\tfor _, neighbor := range graph[role] {\n\t\tif !visited[neighbor] {\n\t\t\t// Recursively visit unvisited neighbor\n\t\t\tif cycle := d.detectCycle(neighbor, graph, visited, recursionStack, path); cycle != nil {\n\t\t\t\treturn cycle\n\t\t\t}\n\t\t} else if recursionStack[neighbor] {\n\t\t\t// Back edge found - cycle detected\n\t\t\t// Find where the cycle starts in the path\n\t\t\tcycleStart := -1\n\t\t\tfor i, p := range path {\n\t\t\t\tif p == neighbor {\n\t\t\t\t\tcycleStart = i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif cycleStart >= 0 {\n\t\t\t\t// Build the cycle path\n\t\t\t\tcyclePath := append([]string{}, path[cycleStart:]...)\n\t\t\t\tcyclePath = append(cyclePath, neighbor)\n\t\t\t\treturn cyclePath\n\t\t\t}\n\t\t}\n\t}\n\n\t// Remove from recursion stack before returning\n\trecursionStack[role] = false\n\treturn nil\n}\n"
  },
  {
    "path": "detector/default_detector_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage detector\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\tdefaultrolemanager \"github.com/casbin/casbin/v3/rbac/default-role-manager\"\n)\n\nfunc TestDefaultDetector_NilRoleManager(t *testing.T) {\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(nil)\n\n\tif err == nil {\n\t\tt.Error(\"Expected error for nil role manager, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"role manager cannot be nil\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'role manager cannot be nil', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_NoCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"bob\", \"user\")\n\t_ = rm.AddLink(\"admin\", \"superuser\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_SimpleCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t_ = rm.AddLink(\"A\", \"B\")\n\t_ = rm.AddLink(\"B\", \"A\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t\t// Should contain both A and B in the cycle\n\t\tif !strings.Contains(errMsg, \"A\") || !strings.Contains(errMsg, \"B\") {\n\t\t\tt.Errorf(\"Expected error message to contain both A and B, got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_ComplexCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t_ = rm.AddLink(\"A\", \"B\")\n\t_ = rm.AddLink(\"B\", \"C\")\n\t_ = rm.AddLink(\"C\", \"A\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t\t// Should contain A, B, and C in the cycle\n\t\tif !strings.Contains(errMsg, \"A\") || !strings.Contains(errMsg, \"B\") || !strings.Contains(errMsg, \"C\") {\n\t\t\tt.Errorf(\"Expected error message to contain A, B, and C, got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_SelfLoop(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t_ = rm.AddLink(\"A\", \"A\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error for self-loop, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_MultipleCycles(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// First cycle: A -> B -> A\n\t_ = rm.AddLink(\"A\", \"B\")\n\t_ = rm.AddLink(\"B\", \"A\")\n\t// Second cycle: C -> D -> C\n\t_ = rm.AddLink(\"C\", \"D\")\n\t_ = rm.AddLink(\"D\", \"C\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_DisconnectedComponents(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// Component 1: alice -> admin -> superuser\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"admin\", \"superuser\")\n\t// Component 2: bob -> user\n\t_ = rm.AddLink(\"bob\", \"user\")\n\t// Component 3: carol -> moderator\n\t_ = rm.AddLink(\"carol\", \"moderator\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle in disconnected components, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_ComplexGraphWithCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// Build a complex graph with one cycle\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\t_ = rm.AddLink(\"u2\", \"g1\")\n\t_ = rm.AddLink(\"g1\", \"g2\")\n\t_ = rm.AddLink(\"g2\", \"g3\")\n\t_ = rm.AddLink(\"g3\", \"g1\") // Creates cycle: g1 -> g2 -> g3 -> g1\n\t_ = rm.AddLink(\"u3\", \"g4\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_LongCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(20)\n\t// Create a long cycle: A -> B -> C -> D -> E -> A\n\t_ = rm.AddLink(\"A\", \"B\")\n\t_ = rm.AddLink(\"B\", \"C\")\n\t_ = rm.AddLink(\"C\", \"D\")\n\t_ = rm.AddLink(\"D\", \"E\")\n\t_ = rm.AddLink(\"E\", \"A\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestDefaultDetector_EmptyRoleManager(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error for empty role manager, but got: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_LargeGraphNoCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(100)\n\n\t// Build a large graph with no cycles: a tree structure\n\t// Create 100 levels: u0 -> u1 -> u2 -> ... -> u99\n\tfor i := 0; i < 99; i++ {\n\t\tuser := fmt.Sprintf(\"u%d\", i)\n\t\trole := fmt.Sprintf(\"u%d\", i+1)\n\t\t_ = rm.AddLink(user, role)\n\t}\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle in large graph, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_LargeGraphWithCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(100)\n\n\t// Build a large graph with a cycle at the end\n\t// Create a chain: u0 -> u1 -> u2 -> ... -> u99 -> u0\n\tfor i := 0; i < 99; i++ {\n\t\tuser := fmt.Sprintf(\"u%d\", i)\n\t\trole := fmt.Sprintf(\"u%d\", i+1)\n\t\t_ = rm.AddLink(user, role)\n\t}\n\t// Add the cycle\n\t_ = rm.AddLink(\"u99\", \"u0\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error in large graph, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\n// Performance test with 10,000 roles.\nfunc TestDefaultDetector_PerformanceLargeGraph(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"Skipping performance test in short mode\")\n\t}\n\n\t// Use a higher maxHierarchyLevel to support deep hierarchies\n\trm := defaultrolemanager.NewRoleManagerImpl(10000)\n\n\t// Build a large tree structure with 10,000 roles\n\t// Each role has up to 3 children\n\tnumRoles := 10000\n\tfor i := 0; i < numRoles; i++ {\n\t\trole := fmt.Sprintf(\"r%d\", i)\n\t\t// Add links to create a tree structure\n\t\tchild1 := (i * 3) + 1\n\t\tchild2 := (i * 3) + 2\n\t\tchild3 := (i * 3) + 3\n\t\tif child1 < numRoles {\n\t\t\t_ = rm.AddLink(fmt.Sprintf(\"r%d\", child1), role)\n\t\t}\n\t\tif child2 < numRoles {\n\t\t\t_ = rm.AddLink(fmt.Sprintf(\"r%d\", child2), role)\n\t\t}\n\t\tif child3 < numRoles {\n\t\t\t_ = rm.AddLink(fmt.Sprintf(\"r%d\", child3), role)\n\t\t}\n\t}\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle in large performance test, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_MultipleInheritance(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// User inherits from multiple roles\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"alice\", \"moderator\")\n\t_ = rm.AddLink(\"admin\", \"superuser\")\n\t_ = rm.AddLink(\"moderator\", \"user\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle with multiple inheritance, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_DiamondPattern(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// Diamond pattern: alice -> admin, alice -> moderator, admin -> superuser, moderator -> superuser\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"alice\", \"moderator\")\n\t_ = rm.AddLink(\"admin\", \"superuser\")\n\t_ = rm.AddLink(\"moderator\", \"superuser\")\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err != nil {\n\t\tt.Errorf(\"Expected no cycle in diamond pattern, but got error: %v\", err)\n\t}\n}\n\nfunc TestDefaultDetector_DiamondPatternWithCycle(t *testing.T) {\n\trm := defaultrolemanager.NewRoleManagerImpl(10)\n\t// Diamond pattern with cycle: alice -> admin, alice -> moderator, admin -> superuser, moderator -> superuser, superuser -> alice\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"alice\", \"moderator\")\n\t_ = rm.AddLink(\"admin\", \"superuser\")\n\t_ = rm.AddLink(\"moderator\", \"superuser\")\n\t_ = rm.AddLink(\"superuser\", \"alice\") // Creates cycle\n\n\tdetector := NewDefaultDetector()\n\terr := detector.Check(rm)\n\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error in diamond pattern with cycle, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'Cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "detector/detector.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage detector\n\nimport \"github.com/casbin/casbin/v3/rbac\"\n\n// Detector defines the interface of a policy consistency checker, currently used to detect RBAC inheritance cycles.\ntype Detector interface {\n\t// Check checks whether the current status of the passed-in RoleManager contains logical errors (e.g., cycles in role inheritance).\n\t// param: rm RoleManager instance\n\t// return: If an error is found, return a descriptive error; otherwise return nil.\n\tCheck(rm rbac.RoleManager) error\n}\n"
  },
  {
    "path": "effector/default_effector.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage effector\n\nimport (\n\t\"errors\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n)\n\n// DefaultEffector is default effector for Casbin.\ntype DefaultEffector struct {\n}\n\n// NewDefaultEffector is the constructor for DefaultEffector.\nfunc NewDefaultEffector() *DefaultEffector {\n\te := DefaultEffector{}\n\treturn &e\n}\n\n// MergeEffects merges all matching results collected by the enforcer into a single decision.\nfunc (e *DefaultEffector) MergeEffects(expr string, effects []Effect, matches []float64, policyIndex int, policyLength int) (Effect, int, error) {\n\tresult := Indeterminate\n\texplainIndex := -1\n\n\tswitch expr {\n\tcase constant.AllowOverrideEffect:\n\t\tif matches[policyIndex] == 0 {\n\t\t\tbreak\n\t\t}\n\t\t// only check the current policyIndex\n\t\tif effects[policyIndex] == Allow {\n\t\t\tresult = Allow\n\t\t\texplainIndex = policyIndex\n\t\t\tbreak\n\t\t}\n\tcase constant.DenyOverrideEffect:\n\t\t// only check the current policyIndex\n\t\tif matches[policyIndex] != 0 && effects[policyIndex] == Deny {\n\t\t\tresult = Deny\n\t\t\texplainIndex = policyIndex\n\t\t\tbreak\n\t\t}\n\t\t// if no deny rules are matched  at last, then allow\n\t\tif policyIndex == policyLength-1 {\n\t\t\tresult = Allow\n\t\t}\n\tcase constant.AllowAndDenyEffect:\n\t\t// short-circuit if matched deny rule\n\t\tif matches[policyIndex] != 0 && effects[policyIndex] == Deny {\n\t\t\tresult = Deny\n\t\t\t// set hit rule to the (first) matched deny rule\n\t\t\texplainIndex = policyIndex\n\t\t\tbreak\n\t\t}\n\n\t\t// short-circuit some effects in the middle\n\t\tif policyIndex < policyLength-1 {\n\t\t\t// choose not to short-circuit\n\t\t\treturn result, explainIndex, nil\n\t\t}\n\t\t// merge all effects at last\n\t\tfor i, eft := range effects {\n\t\t\tif matches[i] == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif eft == Allow {\n\t\t\t\tresult = Allow\n\t\t\t\t// set hit rule to first matched allow rule\n\t\t\t\texplainIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\tcase constant.PriorityEffect, constant.SubjectPriorityEffect:\n\t\t// reverse merge, short-circuit may be earlier\n\t\tfor i := len(effects) - 1; i >= 0; i-- {\n\t\t\tif matches[i] == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif effects[i] != Indeterminate {\n\t\t\t\tif effects[i] == Allow {\n\t\t\t\t\tresult = Allow\n\t\t\t\t} else {\n\t\t\t\t\tresult = Deny\n\t\t\t\t}\n\t\t\t\texplainIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn Deny, -1, errors.New(\"unsupported effect\")\n\t}\n\n\treturn result, explainIndex, nil\n}\n"
  },
  {
    "path": "effector/effector.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage effector //nolint:cyclop // TODO\n\n// Effect is the result for a policy rule.\ntype Effect int\n\n// Values for policy effect.\nconst (\n\tAllow Effect = iota\n\tIndeterminate\n\tDeny\n)\n\n// Effector is the interface for Casbin effectors.\ntype Effector interface {\n\t// MergeEffects merges all matching results collected by the enforcer into a single decision.\n\tMergeEffects(expr string, effects []Effect, matches []float64, policyIndex int, policyLength int) (Effect, int, error)\n}\n"
  },
  {
    "path": "enforcer.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3/detector\"\n\t\"github.com/casbin/casbin/v3/effector\"\n\t\"github.com/casbin/casbin/v3/log\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n\t\"github.com/casbin/casbin/v3/rbac\"\n\tdefaultrolemanager \"github.com/casbin/casbin/v3/rbac/default-role-manager\"\n\t\"github.com/casbin/casbin/v3/util\"\n\n\t\"github.com/casbin/govaluate\"\n)\n\n// Enforcer is the main interface for authorization enforcement and policy management.\ntype Enforcer struct {\n\tmodelPath string\n\tmodel     model.Model\n\tfm        model.FunctionMap\n\teft       effector.Effector\n\n\tadapter    persist.Adapter\n\twatcher    persist.Watcher\n\tdispatcher persist.Dispatcher\n\trmMap      map[string]rbac.RoleManager\n\tcondRmMap  map[string]rbac.ConditionalRoleManager\n\tmatcherMap sync.Map\n\tlogger     log.Logger\n\tdetectors  []detector.Detector\n\n\tenabled              bool\n\tautoSave             bool\n\tautoBuildRoleLinks   bool\n\tautoNotifyWatcher    bool\n\tautoNotifyDispatcher bool\n\tacceptJsonRequest    bool\n\n\taiConfig AIConfig\n}\n\n// EnforceContext is used as the first element of the parameter \"rvals\" in method \"enforce\".\ntype EnforceContext struct {\n\tRType string\n\tPType string\n\tEType string\n\tMType string\n}\n\nfunc (e EnforceContext) GetCacheKey() string {\n\treturn \"EnforceContext{\" + e.RType + \"-\" + e.PType + \"-\" + e.EType + \"-\" + e.MType + \"}\"\n}\n\n// NewEnforcer creates an enforcer via file or DB.\n//\n// File:\n//\n//\te := casbin.NewEnforcer(\"path/to/basic_model.conf\", \"path/to/basic_policy.csv\")\n//\n// MySQL DB:\n//\n//\ta := mysqladapter.NewDBAdapter(\"mysql\", \"mysql_username:mysql_password@tcp(127.0.0.1:3306)/\")\n//\te := casbin.NewEnforcer(\"path/to/basic_model.conf\", a)\nfunc NewEnforcer(params ...interface{}) (*Enforcer, error) {\n\te := &Enforcer{}\n\n\tparsedParamLen := 0\n\tparamLen := len(params)\n\n\tswitch paramLen - parsedParamLen {\n\tcase 2:\n\t\tswitch p0 := params[0].(type) {\n\t\tcase string:\n\t\t\tswitch p1 := params[1].(type) {\n\t\t\tcase string:\n\t\t\t\terr := e.InitWithFile(p0, p1)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\terr := e.InitWithAdapter(p0, p1.(persist.Adapter))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tswitch params[1].(type) {\n\t\t\tcase string:\n\t\t\t\treturn nil, errors.New(\"invalid parameters for enforcer\")\n\t\t\tdefault:\n\t\t\t\terr := e.InitWithModelAndAdapter(p0.(model.Model), params[1].(persist.Adapter))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase 1:\n\t\tswitch p0 := params[0].(type) {\n\t\tcase string:\n\t\t\terr := e.InitWithFile(p0, \"\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\tdefault:\n\t\t\terr := e.InitWithModelAndAdapter(p0.(model.Model), nil)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\tcase 0:\n\t\treturn e, nil\n\tdefault:\n\t\treturn nil, errors.New(\"invalid parameters for enforcer\")\n\t}\n\n\treturn e, nil\n}\n\n// InitWithFile initializes an enforcer with a model file and a policy file.\nfunc (e *Enforcer) InitWithFile(modelPath string, policyPath string) error {\n\ta := fileadapter.NewAdapter(policyPath)\n\treturn e.InitWithAdapter(modelPath, a)\n}\n\n// InitWithAdapter initializes an enforcer with a database adapter.\nfunc (e *Enforcer) InitWithAdapter(modelPath string, adapter persist.Adapter) error {\n\tm, err := model.NewModelFromFile(modelPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = e.InitWithModelAndAdapter(m, adapter)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\te.modelPath = modelPath\n\treturn nil\n}\n\n// InitWithModelAndAdapter initializes an enforcer with a model and a database adapter.\nfunc (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error {\n\te.adapter = adapter\n\n\te.model = m\n\te.model.PrintModel()\n\te.fm = model.LoadFunctionMap()\n\n\te.initialize()\n\n\t// Do not initialize the full policy when using a filtered adapter\n\tfa, ok := e.adapter.(persist.FilteredAdapter)\n\tif e.adapter != nil && (!ok || ok && !fa.IsFiltered()) {\n\t\terr := e.LoadPolicy()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (e *Enforcer) initialize() {\n\te.rmMap = map[string]rbac.RoleManager{}\n\te.condRmMap = map[string]rbac.ConditionalRoleManager{}\n\te.eft = effector.NewDefaultEffector()\n\te.watcher = nil\n\te.matcherMap = sync.Map{}\n\n\te.enabled = true\n\te.autoSave = true\n\te.autoBuildRoleLinks = true\n\te.autoNotifyWatcher = true\n\te.autoNotifyDispatcher = true\n\te.initRmMap()\n\n\t// Initialize detectors with default detector if not already set\n\tif e.detectors == nil {\n\t\te.detectors = []detector.Detector{detector.NewDefaultDetector()}\n\t}\n}\n\n// LoadModel reloads the model from the model CONF file.\n// Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy().\nfunc (e *Enforcer) LoadModel() error {\n\tvar err error\n\te.model, err = model.NewModelFromFile(e.modelPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\te.model.PrintModel()\n\te.fm = model.LoadFunctionMap()\n\n\te.initialize()\n\n\treturn nil\n}\n\n// GetModel gets the current model.\nfunc (e *Enforcer) GetModel() model.Model {\n\treturn e.model\n}\n\n// SetModel sets the current model.\nfunc (e *Enforcer) SetModel(m model.Model) {\n\te.model = m\n\te.fm = model.LoadFunctionMap()\n\n\te.initialize()\n}\n\n// GetAdapter gets the current adapter.\nfunc (e *Enforcer) GetAdapter() persist.Adapter {\n\treturn e.adapter\n}\n\n// SetAdapter sets the current adapter.\nfunc (e *Enforcer) SetAdapter(adapter persist.Adapter) {\n\te.adapter = adapter\n}\n\n// SetWatcher sets the current watcher.\nfunc (e *Enforcer) SetWatcher(watcher persist.Watcher) error {\n\te.watcher = watcher\n\tif _, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t// The callback of WatcherEx has no generic implementation.\n\t\treturn nil\n\t} else {\n\t\t// In case the Watcher wants to use a customized callback function, call `SetUpdateCallback` after `SetWatcher`.\n\t\treturn watcher.SetUpdateCallback(func(string) { _ = e.LoadPolicy() })\n\t}\n}\n\n// GetRoleManager gets the current role manager.\nfunc (e *Enforcer) GetRoleManager() rbac.RoleManager {\n\tif e.rmMap != nil && e.rmMap[\"g\"] != nil {\n\t\treturn e.rmMap[\"g\"]\n\t} else if e.condRmMap != nil && e.condRmMap[\"g\"] != nil {\n\t\treturn e.condRmMap[\"g\"]\n\t} else {\n\t\treturn nil\n\t}\n}\n\n// GetNamedRoleManager gets the role manager for the named policy.\nfunc (e *Enforcer) GetNamedRoleManager(ptype string) rbac.RoleManager {\n\tif e.rmMap != nil && e.rmMap[ptype] != nil {\n\t\treturn e.rmMap[ptype]\n\t} else if e.condRmMap != nil && e.condRmMap[ptype] != nil {\n\t\treturn e.condRmMap[ptype]\n\t} else {\n\t\treturn nil\n\t}\n}\n\n// SetRoleManager sets the current role manager.\nfunc (e *Enforcer) SetRoleManager(rm rbac.RoleManager) {\n\te.invalidateMatcherMap()\n\te.rmMap[\"g\"] = rm\n}\n\n// SetNamedRoleManager sets the role manager for the named policy.\nfunc (e *Enforcer) SetNamedRoleManager(ptype string, rm rbac.RoleManager) {\n\te.invalidateMatcherMap()\n\te.rmMap[ptype] = rm\n}\n\n// SetEffector sets the current effector.\nfunc (e *Enforcer) SetEffector(eft effector.Effector) {\n\te.eft = eft\n}\n\n// SetLogger sets the logger for the enforcer.\nfunc (e *Enforcer) SetLogger(logger log.Logger) {\n\te.logger = logger\n}\n\n// SetDetector sets a single detector for the enforcer.\nfunc (e *Enforcer) SetDetector(d detector.Detector) {\n\te.detectors = []detector.Detector{d}\n}\n\n// SetDetectors sets multiple detectors for the enforcer.\nfunc (e *Enforcer) SetDetectors(detectors []detector.Detector) {\n\te.detectors = detectors\n}\n\n// RunDetections runs all detectors on all role managers.\n// Returns the first error encountered, or nil if all checks pass.\n// Silently skips role managers that don't support the required iteration methods.\nfunc (e *Enforcer) RunDetections() error {\n\tif e.detectors == nil || len(e.detectors) == 0 {\n\t\treturn nil\n\t}\n\n\t// Run detectors on all role managers\n\tfor _, rm := range e.rmMap {\n\t\tfor _, d := range e.detectors {\n\t\t\terr := d.Check(rm)\n\t\t\t// Skip if the role manager doesn't support the required iteration or is not initialized\n\t\t\tif err != nil && (strings.Contains(err.Error(), \"does not support Range iteration\") ||\n\t\t\t\tstrings.Contains(err.Error(), \"not properly initialized\")) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Run detectors on all conditional role managers\n\tfor _, crm := range e.condRmMap {\n\t\tfor _, d := range e.detectors {\n\t\t\terr := d.Check(crm)\n\t\t\t// Skip if the role manager doesn't support the required iteration or is not initialized\n\t\t\tif err != nil && (strings.Contains(err.Error(), \"does not support Range iteration\") ||\n\t\t\t\tstrings.Contains(err.Error(), \"not properly initialized\")) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ClearPolicy clears all policy.\nfunc (e *Enforcer) ClearPolicy() {\n\te.invalidateMatcherMap()\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\t_ = e.dispatcher.ClearPolicy()\n\t\treturn\n\t}\n\te.model.ClearPolicy()\n}\n\n// LoadPolicy reloads the policy from file/database.\nfunc (e *Enforcer) LoadPolicy() error {\n\tlogEntry := e.onLogBeforeEventInLoadPolicy()\n\n\tnewModel, err := e.loadPolicyFromAdapter(e.model)\n\tif err != nil {\n\t\te.onLogAfterEventWithError(logEntry, err)\n\t\treturn err\n\t}\n\terr = e.applyModifiedModel(newModel)\n\tif err != nil {\n\t\te.onLogAfterEventWithError(logEntry, err)\n\t\treturn err\n\t}\n\n\te.onLogAfterEventInLoadPolicy(logEntry, newModel)\n\n\t// Run detectors after all policy rules are loaded\n\terr = e.RunDetections()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (e *Enforcer) loadPolicyFromAdapter(baseModel model.Model) (model.Model, error) {\n\tnewModel := baseModel.Copy()\n\tnewModel.ClearPolicy()\n\n\tif err := e.adapter.LoadPolicy(newModel); err != nil && err.Error() != \"invalid file path, file path cannot be empty\" {\n\t\treturn nil, err\n\t}\n\n\tif err := newModel.SortPoliciesBySubjectHierarchy(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := newModel.SortPoliciesByPriority(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newModel, nil\n}\n\nfunc (e *Enforcer) applyModifiedModel(newModel model.Model) error {\n\tvar err error\n\tneedToRebuild := false\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif e.autoBuildRoleLinks && needToRebuild {\n\t\t\t\t_ = e.BuildRoleLinks()\n\t\t\t}\n\t\t}\n\t}()\n\n\tif e.autoBuildRoleLinks {\n\t\tneedToRebuild = true\n\n\t\tif err := e.rebuildRoleLinks(newModel); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := e.rebuildConditionalRoleLinks(newModel); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\te.model = newModel\n\te.invalidateMatcherMap()\n\treturn nil\n}\n\nfunc (e *Enforcer) rebuildRoleLinks(newModel model.Model) error {\n\tif len(e.rmMap) != 0 {\n\t\tfor _, rm := range e.rmMap {\n\t\t\terr := rm.Clear()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\terr := newModel.BuildRoleLinks(e.rmMap)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (e *Enforcer) rebuildConditionalRoleLinks(newModel model.Model) error {\n\tif len(e.condRmMap) != 0 {\n\t\tfor _, crm := range e.condRmMap {\n\t\t\terr := crm.Clear()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\terr := newModel.BuildConditionalRoleLinks(e.condRmMap)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (e *Enforcer) loadFilteredPolicy(filter interface{}) error {\n\te.invalidateMatcherMap()\n\n\tvar filteredAdapter persist.FilteredAdapter\n\n\t// Attempt to cast the Adapter as a FilteredAdapter\n\tswitch adapter := e.adapter.(type) {\n\tcase persist.FilteredAdapter:\n\t\tfilteredAdapter = adapter\n\tdefault:\n\t\treturn errors.New(\"filtered policies are not supported by this adapter\")\n\t}\n\tif err := filteredAdapter.LoadFilteredPolicy(e.model, filter); err != nil && err.Error() != \"invalid file path, file path cannot be empty\" {\n\t\treturn err\n\t}\n\n\tif err := e.model.SortPoliciesBySubjectHierarchy(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := e.model.SortPoliciesByPriority(); err != nil {\n\t\treturn err\n\t}\n\n\te.initRmMap()\n\te.model.PrintPolicy()\n\tif e.autoBuildRoleLinks {\n\t\terr := e.BuildRoleLinks()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// LoadFilteredPolicy reloads a filtered policy from file/database.\nfunc (e *Enforcer) LoadFilteredPolicy(filter interface{}) error {\n\te.model.ClearPolicy()\n\n\treturn e.loadFilteredPolicy(filter)\n}\n\n// LoadIncrementalFilteredPolicy append a filtered policy from file/database.\nfunc (e *Enforcer) LoadIncrementalFilteredPolicy(filter interface{}) error {\n\treturn e.loadFilteredPolicy(filter)\n}\n\n// IsFiltered returns true if the loaded policy has been filtered.\nfunc (e *Enforcer) IsFiltered() bool {\n\tfilteredAdapter, ok := e.adapter.(persist.FilteredAdapter)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn filteredAdapter.IsFiltered()\n}\n\n// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database.\nfunc (e *Enforcer) SavePolicy() error {\n\tlogEntry := e.onLogBeforeEventInSavePolicy()\n\n\tif e.IsFiltered() {\n\t\terr := errors.New(\"cannot save a filtered policy\")\n\t\te.onLogAfterEventWithError(logEntry, err)\n\t\treturn err\n\t}\n\tif err := e.adapter.SavePolicy(e.model); err != nil {\n\t\te.onLogAfterEventWithError(logEntry, err)\n\t\treturn err\n\t}\n\n\te.onLogAfterEventInSavePolicy(logEntry)\n\n\tif e.watcher != nil {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForSavePolicy(e.model)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// getDomainTokens extracts domain token names from request and policy definitions.\n// Returns empty strings if tokens cannot be found.\nfunc (e *Enforcer) getDomainTokens() (rDomainToken, pDomainToken string) {\n\tif rAssertion, ok := e.model[\"r\"][\"r\"]; ok && len(rAssertion.Tokens) > 1 {\n\t\trDomainToken = rAssertion.Tokens[1]\n\t}\n\tif pAssertion, ok := e.model[\"p\"][\"p\"]; ok && len(pAssertion.Tokens) > 1 {\n\t\tpDomainToken = pAssertion.Tokens[1]\n\t}\n\treturn rDomainToken, pDomainToken\n}\n\n// registerDomainMatchingFunc registers domain matching function if the matcher uses keyMatch for domains.\nfunc (e *Enforcer) registerDomainMatchingFunc(ptype string) {\n\t// Dynamically detect the domain token name from the model definition.\n\t// In RBAC with domains, the domain is typically the second parameter (index 1)\n\t// in both request and policy definitions (e.g., r = sub, dom, obj, act).\n\t// We extract the actual token names to support arbitrary domain parameter names.\n\trDomainToken, pDomainToken := e.getDomainTokens()\n\tif rDomainToken == \"\" || pDomainToken == \"\" {\n\t\treturn\n\t}\n\n\tmatchFun := fmt.Sprintf(\"keyMatch(%s, %s)\", rDomainToken, pDomainToken)\n\tif strings.Contains(e.model[\"m\"][\"m\"].Value, matchFun) {\n\t\te.AddNamedDomainMatchingFunc(ptype, \"g\", util.KeyMatch)\n\t}\n}\n\nfunc (e *Enforcer) initRmMap() {\n\tfor ptype, assertion := range e.model[\"g\"] {\n\t\tif rm, ok := e.rmMap[ptype]; ok {\n\t\t\t_ = rm.Clear()\n\t\t\tcontinue\n\t\t}\n\t\tif len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) == 0 {\n\t\t\tassertion.RM = defaultrolemanager.NewRoleManagerImpl(10)\n\t\t\te.rmMap[ptype] = assertion.RM\n\t\t}\n\t\tif len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) != 0 {\n\t\t\tassertion.CondRM = defaultrolemanager.NewConditionalRoleManager(10)\n\t\t\te.condRmMap[ptype] = assertion.CondRM\n\t\t}\n\t\tif len(assertion.Tokens) > 2 {\n\t\t\tif len(assertion.ParamsTokens) == 0 {\n\t\t\t\tassertion.RM = defaultrolemanager.NewRoleManager(10)\n\t\t\t\te.rmMap[ptype] = assertion.RM\n\t\t\t} else {\n\t\t\t\tassertion.CondRM = defaultrolemanager.NewConditionalDomainManager(10)\n\t\t\t\te.condRmMap[ptype] = assertion.CondRM\n\t\t\t}\n\t\t\te.registerDomainMatchingFunc(ptype)\n\t\t}\n\t}\n}\n\n// EnableEnforce changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function.\nfunc (e *Enforcer) EnableEnforce(enable bool) {\n\te.enabled = enable\n}\n\n// EnableAutoNotifyWatcher controls whether to save a policy rule automatically notify the Watcher when it is added or removed.\nfunc (e *Enforcer) EnableAutoNotifyWatcher(enable bool) {\n\te.autoNotifyWatcher = enable\n}\n\n// EnableAutoNotifyDispatcher controls whether to save a policy rule automatically notify the Dispatcher when it is added or removed.\nfunc (e *Enforcer) EnableAutoNotifyDispatcher(enable bool) {\n\te.autoNotifyDispatcher = enable\n}\n\n// EnableAutoSave controls whether to save a policy rule automatically to the adapter when it is added or removed.\nfunc (e *Enforcer) EnableAutoSave(autoSave bool) {\n\te.autoSave = autoSave\n}\n\n// EnableAutoBuildRoleLinks controls whether to rebuild the role inheritance relations when a role is added or deleted.\nfunc (e *Enforcer) EnableAutoBuildRoleLinks(autoBuildRoleLinks bool) {\n\te.autoBuildRoleLinks = autoBuildRoleLinks\n}\n\n// EnableAcceptJsonRequest controls whether to accept json as a request parameter.\nfunc (e *Enforcer) EnableAcceptJsonRequest(acceptJsonRequest bool) {\n\te.acceptJsonRequest = acceptJsonRequest\n}\n\n// BuildRoleLinks manually rebuild the role inheritance relations.\nfunc (e *Enforcer) BuildRoleLinks() error {\n\te.invalidateMatcherMap()\n\tif e.rmMap == nil {\n\t\treturn errors.New(\"rmMap is nil\")\n\t}\n\tfor _, rm := range e.rmMap {\n\t\terr := rm.Clear()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn e.model.BuildRoleLinks(e.rmMap)\n}\n\n// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.\nfunc (e *Enforcer) BuildIncrementalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error {\n\te.invalidateMatcherMap()\n\treturn e.model.BuildIncrementalRoleLinks(e.rmMap, op, \"g\", ptype, rules)\n}\n\n// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations with conditions.\nfunc (e *Enforcer) BuildIncrementalConditionalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error {\n\te.invalidateMatcherMap()\n\treturn e.model.BuildIncrementalConditionalRoleLinks(e.condRmMap, op, \"g\", ptype, rules)\n}\n\n// NewEnforceContext Create a default structure based on the suffix.\nfunc NewEnforceContext(suffix string) EnforceContext {\n\treturn EnforceContext{\n\t\tRType: \"r\" + suffix,\n\t\tPType: \"p\" + suffix,\n\t\tEType: \"e\" + suffix,\n\t\tMType: \"m\" + suffix,\n\t}\n}\n\nfunc (e *Enforcer) invalidateMatcherMap() {\n\te.matcherMap = sync.Map{}\n}\n\n// enforce use a custom matcher to decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is \"\".\nfunc (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interface{}) (ok bool, err error) { //nolint:funlen,cyclop,gocyclo // TODO: reduce function complexity\n\tlogEntry := e.onLogBeforeEventInEnforce(rvals)\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"panic: %v\\n%s\", r, debug.Stack())\n\t\t\tif e.logger != nil && logEntry != nil {\n\t\t\t\tlogEntry.Error = err\n\t\t\t}\n\t\t}\n\t\te.onLogAfterEventInEnforce(logEntry, ok)\n\t}()\n\n\tif !e.enabled {\n\t\treturn true, nil\n\t}\n\n\tfunctions := e.fm.GetFunctions()\n\tif _, ok := e.model[\"g\"]; ok {\n\t\tfor key, ast := range e.model[\"g\"] {\n\t\t\t// g must be a normal role definition (ast.RM != nil)\n\t\t\t//   or a conditional role definition (ast.CondRM != nil)\n\t\t\t// ast.RM and ast.CondRM shouldn't be nil at the same time\n\t\t\tif ast.RM != nil {\n\t\t\t\tfunctions[key] = util.GenerateGFunction(ast.RM)\n\t\t\t}\n\t\t\tif ast.CondRM != nil {\n\t\t\t\tfunctions[key] = util.GenerateConditionalGFunction(ast.CondRM)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar (\n\t\trType = \"r\"\n\t\tpType = \"p\"\n\t\teType = \"e\"\n\t\tmType = \"m\"\n\t)\n\tif len(rvals) != 0 {\n\t\tswitch rvals[0].(type) {\n\t\tcase EnforceContext:\n\t\t\tenforceContext := rvals[0].(EnforceContext)\n\t\t\trType = enforceContext.RType\n\t\t\tpType = enforceContext.PType\n\t\t\teType = enforceContext.EType\n\t\t\tmType = enforceContext.MType\n\t\t\trvals = rvals[1:]\n\t\tdefault:\n\t\t\tbreak\n\t\t}\n\t}\n\n\tvar expString string\n\tif matcher == \"\" {\n\t\texpString = e.model[\"m\"][mType].Value\n\t} else {\n\t\t// For custom matchers provided at runtime, escape backslashes in string literals\n\t\texpString = util.EscapeStringLiterals(util.RemoveComments(util.EscapeAssertion(matcher)))\n\t}\n\n\trTokens := make(map[string]int, len(e.model[\"r\"][rType].Tokens))\n\tfor i, token := range e.model[\"r\"][rType].Tokens {\n\t\trTokens[token] = i\n\t}\n\tpTokens := make(map[string]int, len(e.model[\"p\"][pType].Tokens))\n\tfor i, token := range e.model[\"p\"][pType].Tokens {\n\t\tpTokens[token] = i\n\t}\n\n\tif e.acceptJsonRequest {\n\t\t// try to parse all request values from json to map[string]interface{}\n\t\tfor i, rval := range rvals {\n\t\t\tswitch rval := rval.(type) {\n\t\t\tcase string:\n\t\t\t\t// Only attempt JSON parsing for strings that look like JSON objects or arrays\n\t\t\t\tif len(rval) > 0 && (rval[0] == '{' || rval[0] == '[') {\n\t\t\t\t\tvar mapValue map[string]interface{}\n\t\t\t\t\tmapValue, err = util.JsonToMap(rval)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t// Return a clear error when JSON-like string fails to parse\n\t\t\t\t\t\treturn false, fmt.Errorf(\"failed to parse JSON parameter at index %d: %w\", i, err)\n\t\t\t\t\t}\n\t\t\t\t\trvals[i] = mapValue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tparameters := enforceParameters{\n\t\trTokens: rTokens,\n\t\trVals:   rvals,\n\n\t\tpTokens: pTokens,\n\t}\n\n\thasEval := util.HasEval(expString)\n\tif hasEval {\n\t\tfunctions[\"eval\"] = generateEvalFunction(functions, &parameters)\n\t}\n\tvar expression *govaluate.EvaluableExpression\n\texpression, err = e.getAndStoreMatcherExpression(hasEval, expString, functions)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif len(e.model[\"r\"][rType].Tokens) != len(rvals) {\n\t\treturn false, fmt.Errorf(\n\t\t\t\"invalid request size: expected %d, got %d, rvals: %v\",\n\t\t\tlen(e.model[\"r\"][rType].Tokens),\n\t\t\tlen(rvals),\n\t\t\trvals)\n\t}\n\n\tvar policyEffects []effector.Effect\n\tvar matcherResults []float64\n\n\tvar effect effector.Effect\n\tvar explainIndex int\n\n\tif policyLen := len(e.model[\"p\"][pType].Policy); policyLen != 0 && strings.Contains(expString, pType+\"_\") { //nolint:nestif // TODO: reduce function complexity\n\t\tpolicyEffects = make([]effector.Effect, policyLen)\n\t\tmatcherResults = make([]float64, policyLen)\n\n\t\tfor policyIndex, pvals := range e.model[\"p\"][pType].Policy {\n\t\t\t// log.LogPrint(\"Policy Rule: \", pvals)\n\t\t\tif len(e.model[\"p\"][pType].Tokens) != len(pvals) {\n\t\t\t\treturn false, fmt.Errorf(\n\t\t\t\t\t\"invalid policy size: expected %d, got %d, pvals: %v\",\n\t\t\t\t\tlen(e.model[\"p\"][pType].Tokens),\n\t\t\t\t\tlen(pvals),\n\t\t\t\t\tpvals)\n\t\t\t}\n\n\t\t\tparameters.pVals = pvals\n\n\t\t\tresult, err := expression.Eval(parameters)\n\t\t\t// log.LogPrint(\"Result: \", result)\n\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\t// set to no-match at first\n\t\t\tmatcherResults[policyIndex] = 0\n\t\t\tswitch result := result.(type) {\n\t\t\tcase bool:\n\t\t\t\tif result {\n\t\t\t\t\tmatcherResults[policyIndex] = 1\n\t\t\t\t}\n\t\t\tcase float64:\n\t\t\t\tif result != 0 {\n\t\t\t\t\tmatcherResults[policyIndex] = 1\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn false, errors.New(\"matcher result should be bool, int or float\")\n\t\t\t}\n\n\t\t\tif j, ok := parameters.pTokens[pType+\"_eft\"]; ok {\n\t\t\t\teft := parameters.pVals[j]\n\t\t\t\tif eft == \"allow\" {\n\t\t\t\t\tpolicyEffects[policyIndex] = effector.Allow\n\t\t\t\t} else if eft == \"deny\" {\n\t\t\t\t\tpolicyEffects[policyIndex] = effector.Deny\n\t\t\t\t} else {\n\t\t\t\t\tpolicyEffects[policyIndex] = effector.Indeterminate\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpolicyEffects[policyIndex] = effector.Allow\n\t\t\t}\n\n\t\t\t// if e.model[\"e\"][\"e\"].Value == \"priority(p_eft) || deny\" {\n\t\t\t//\tbreak\n\t\t\t// }\n\n\t\t\teffect, explainIndex, err = e.eft.MergeEffects(e.model[\"e\"][eType].Value, policyEffects, matcherResults, policyIndex, policyLen)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tif effect != effector.Indeterminate {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif hasEval && len(e.model[\"p\"][pType].Policy) == 0 {\n\t\t\treturn false, errors.New(\"please make sure rule exists in policy when using eval() in matcher\")\n\t\t}\n\n\t\tpolicyEffects = make([]effector.Effect, 1)\n\t\tmatcherResults = make([]float64, 1)\n\t\tmatcherResults[0] = 1\n\n\t\tparameters.pVals = make([]string, len(parameters.pTokens))\n\n\t\tresult, err := expression.Eval(parameters)\n\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif result.(bool) {\n\t\t\tpolicyEffects[0] = effector.Allow\n\t\t} else {\n\t\t\tpolicyEffects[0] = effector.Indeterminate\n\t\t}\n\n\t\teffect, explainIndex, err = e.eft.MergeEffects(e.model[\"e\"][eType].Value, policyEffects, matcherResults, 0, 1)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tif explains != nil {\n\t\tif explainIndex != -1 && len(e.model[\"p\"][pType].Policy) > explainIndex {\n\t\t\t*explains = e.model[\"p\"][pType].Policy[explainIndex]\n\t\t}\n\t}\n\n\t// effect -> result\n\tresult := false\n\tif effect == effector.Allow {\n\t\tresult = true\n\t}\n\n\treturn result, nil\n}\n\nfunc (e *Enforcer) getAndStoreMatcherExpression(hasEval bool, expString string, functions map[string]govaluate.ExpressionFunction) (*govaluate.EvaluableExpression, error) {\n\tvar expression *govaluate.EvaluableExpression\n\tvar err error\n\tvar cachedExpression, isPresent = e.matcherMap.Load(expString)\n\n\tif !hasEval && isPresent {\n\t\texpression = cachedExpression.(*govaluate.EvaluableExpression)\n\t} else {\n\t\texpression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\te.matcherMap.Store(expString, expression)\n\t}\n\treturn expression, nil\n}\n\n// Enforce decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).\nfunc (e *Enforcer) Enforce(rvals ...interface{}) (bool, error) {\n\treturn e.enforce(\"\", nil, rvals...)\n}\n\n// 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 \"\".\nfunc (e *Enforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) {\n\treturn e.enforce(matcher, nil, rvals...)\n}\n\n// EnforceEx explain enforcement by informing matched rules.\nfunc (e *Enforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) {\n\texplain := []string{}\n\tresult, err := e.enforce(\"\", &explain, rvals...)\n\treturn result, explain, err\n}\n\n// EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules.\nfunc (e *Enforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) {\n\texplain := []string{}\n\tresult, err := e.enforce(matcher, &explain, rvals...)\n\treturn result, explain, err\n}\n\n// BatchEnforce enforce in batches.\nfunc (e *Enforcer) BatchEnforce(requests [][]interface{}) ([]bool, error) {\n\tvar results []bool\n\tfor _, request := range requests {\n\t\tresult, err := e.enforce(\"\", nil, request...)\n\t\tif err != nil {\n\t\t\treturn results, err\n\t\t}\n\t\tresults = append(results, result)\n\t}\n\treturn results, nil\n}\n\n// BatchEnforceWithMatcher enforce with matcher in batches.\nfunc (e *Enforcer) BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error) {\n\tvar results []bool\n\tfor _, request := range requests {\n\t\tresult, err := e.enforce(matcher, nil, request...)\n\t\tif err != nil {\n\t\t\treturn results, err\n\t\t}\n\t\tresults = append(results, result)\n\t}\n\treturn results, nil\n}\n\n// AddNamedMatchingFunc add MatchingFunc by ptype RoleManager.\nfunc (e *Enforcer) AddNamedMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool {\n\tif rm, ok := e.rmMap[ptype]; ok {\n\t\trm.AddMatchingFunc(name, fn)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager.\nfunc (e *Enforcer) AddNamedDomainMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool {\n\tif rm, ok := e.rmMap[ptype]; ok {\n\t\trm.AddDomainMatchingFunc(name, fn)\n\t\treturn true\n\t}\n\tif condRm, ok := e.condRmMap[ptype]; ok {\n\t\tcondRm.AddDomainMatchingFunc(name, fn)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName,\n// when fn returns true, Link is valid, otherwise invalid.\nfunc (e *Enforcer) AddNamedLinkConditionFunc(ptype, user, role string, fn rbac.LinkConditionFunc) bool {\n\tif rm, ok := e.condRmMap[ptype]; ok {\n\t\trm.AddLinkConditionFunc(user, role, fn)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},\n// when fn returns true, Link is valid, otherwise invalid.\nfunc (e *Enforcer) AddNamedDomainLinkConditionFunc(ptype, user, role string, domain string, fn rbac.LinkConditionFunc) bool {\n\tif rm, ok := e.condRmMap[ptype]; ok {\n\t\trm.AddDomainLinkConditionFunc(user, role, domain, fn)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName.\nfunc (e *Enforcer) SetNamedLinkConditionFuncParams(ptype, user, role string, params ...string) bool {\n\tif rm, ok := e.condRmMap[ptype]; ok {\n\t\trm.SetLinkConditionFuncParams(user, role, params...)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn\n// for Link userName->{roleName, domain}.\nfunc (e *Enforcer) SetNamedDomainLinkConditionFuncParams(ptype, user, role, domain string, params ...string) bool {\n\tif rm, ok := e.condRmMap[ptype]; ok {\n\t\trm.SetDomainLinkConditionFuncParams(user, role, domain, params...)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// assumes bounds have already been checked.\ntype enforceParameters struct {\n\trTokens map[string]int\n\trVals   []interface{}\n\n\tpTokens map[string]int\n\tpVals   []string\n}\n\n// implements govaluate.Parameters.\nfunc (p enforceParameters) Get(name string) (interface{}, error) {\n\tif name == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tswitch name[0] {\n\tcase 'p':\n\t\ti, ok := p.pTokens[name]\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"No parameter '\" + name + \"' found.\")\n\t\t}\n\t\treturn p.pVals[i], nil\n\tcase 'r':\n\t\ti, ok := p.rTokens[name]\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"No parameter '\" + name + \"' found.\")\n\t\t}\n\t\treturn p.rVals[i], nil\n\tdefault:\n\t\treturn nil, errors.New(\"No parameter '\" + name + \"' found.\")\n\t}\n}\n\nfunc generateEvalFunction(functions map[string]govaluate.ExpressionFunction, parameters *enforceParameters) govaluate.ExpressionFunction {\n\treturn func(args ...interface{}) (interface{}, error) {\n\t\tif len(args) != 1 {\n\t\t\treturn nil, fmt.Errorf(\"function eval(subrule string) expected %d arguments, but got %d\", 1, len(args))\n\t\t}\n\n\t\texpression, ok := args[0].(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"argument of eval(subrule string) must be a string\")\n\t\t}\n\t\texpression = util.EscapeAssertion(expression)\n\t\texpr, err := govaluate.NewEvaluableExpressionWithFunctions(expression, functions)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error while parsing eval parameter: %s, %s\", expression, err.Error())\n\t\t}\n\t\treturn expr.Eval(parameters)\n\t}\n}\n"
  },
  {
    "path": "enforcer_backslash_test.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n)\n\n// TestBackslashHandlingConsistency tests that backslashes in string literals\n// within matcher expressions are handled consistently with CSV-parsed values.\n// This addresses the issue where govaluate interprets escape sequences in\n// string literals, but CSV parsing treats backslashes as literal characters.\nfunc TestBackslashHandlingConsistency(t *testing.T) {\n\t// Test case 1: Literal string in matcher should match CSV-parsed request\n\tt.Run(\"LiteralInMatcher\", func(t *testing.T) {\n\t\tm := model.NewModel()\n\t\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\t\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\t\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\t\t// User writes '\\1\\2' in matcher - should be treated as literal backslashes\n\t\tm.AddDef(\"m\", \"m\", \"regexMatch('\\\\1\\\\2', p.obj)\")\n\n\t\te, err := NewEnforcer(m, fileadapter.NewAdapter(\"examples/basic_policy.csv\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Add a policy with a regex pattern containing backslashes\n\t\t// CSV format: \"\\\\[0-9]+\\\\\" means literal string with 4 backslashes\n\t\t_, err = e.AddPolicy(\"filename\", \"\\\\\\\\[0-9]+\\\\\\\\\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// This should match because '\\1\\2' after escaping becomes \\1\\2,\n\t\t// and the pattern \\\\[0-9]+\\\\ matches strings like \\1\\ (which is a substring of \\1\\2)\n\t\tresult, err := e.Enforce(\"filename\", \"dummy\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif !result {\n\t\t\tt.Errorf(\"Expected true, got false - literal '\\\\1\\\\2' should match after escape processing\")\n\t\t}\n\t})\n\n\t// Test case 2: Request parameter should match policy with same backslash content\n\tt.Run(\"RequestParameterVsPolicy\", func(t *testing.T) {\n\t\tm := model.NewModel()\n\t\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\t\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\t\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\t\tm.AddDef(\"m\", \"m\", \"regexMatch(r.obj, p.obj)\")\n\n\t\te, err := NewEnforcer(m, fileadapter.NewAdapter(\"examples/basic_policy.csv\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Add policy with regex pattern\n\t\t_, err = e.AddPolicy(\"filename\", \"\\\\\\\\[0-9]+\\\\\\\\\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Request with backslashes - simulating CSV input \"\\1\\2\" which becomes \\1\\2\n\t\tresult, err := e.Enforce(\"filename\", \"\\\\1\\\\2\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif !result {\n\t\t\tt.Errorf(\"Expected true, got false - request \\\\1\\\\2 should match regex pattern\")\n\t\t}\n\t})\n\n\t// Test case 3: Both approaches should give the same result\n\tt.Run(\"ConsistencyBetweenLiteralAndParameter\", func(t *testing.T) {\n\t\t// Create two enforcers with different matchers\n\t\tm1 := model.NewModel()\n\t\tm1.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\t\tm1.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\t\tm1.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\t\tm1.AddDef(\"m\", \"m\", \"regexMatch('\\\\1\\\\2', p.obj)\")\n\n\t\tm2 := model.NewModel()\n\t\tm2.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\t\tm2.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\t\tm2.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\t\tm2.AddDef(\"m\", \"m\", \"regexMatch(r.obj, p.obj)\")\n\n\t\te1, err := NewEnforcer(m1, fileadapter.NewAdapter(\"examples/basic_policy.csv\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\te2, err := NewEnforcer(m2, fileadapter.NewAdapter(\"examples/basic_policy.csv\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Add same policy to both\n\t\tpattern := \"\\\\\\\\[0-9]+\\\\\\\\\"\n\t\t_, err = e1.AddPolicy(\"filename\", pattern, \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t_, err = e2.AddPolicy(\"filename\", pattern, \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Test with the same request\n\t\tresult1, err := e1.Enforce(\"filename\", \"dummy\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tresult2, err := e2.Enforce(\"filename\", \"\\\\1\\\\2\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif result1 != result2 {\n\t\t\tt.Errorf(\"Inconsistent results: literal in matcher gave %v, parameter gave %v\", result1, result2)\n\t\t}\n\t})\n\n\t// Test case 4: Simple equality check with backslashes\n\tt.Run(\"SimpleEqualityWithBackslashes\", func(t *testing.T) {\n\t\tm := model.NewModel()\n\t\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\t\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\t\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\t\t// In Go source, '\\test' (one backslash in the actual string) represents\n\t\t// what would be typed in a web form. After escape processing, it will match\n\t\t// the CSV-parsed value \"\\test\" (one backslash).\n\t\t// Note: In Go source, we write \"r.obj == '\\\\test'\" which is a Go string\n\t\t// containing the text: r.obj == '\\test' (with ONE backslash in the string content)\n\t\tm.AddDef(\"m\", \"m\", \"r.obj == '\\\\test' && p.sub == r.sub\")\n\n\t\te, err := NewEnforcer(m, fileadapter.NewAdapter(\"examples/basic_policy.csv\"))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t_, err = e.AddPolicy(\"alice\", \"any\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Request with literal backslash from CSV would be \"\\test\"\n\t\t// In Go source, we write \"\\\\test\" which represents the string \\test (one backslash)\n\t\tresult, err := e.Enforce(\"alice\", \"\\\\test\", \"read\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif !result {\n\t\t\tt.Errorf(\"Expected true - literal '\\\\test' should equal request parameter \\\\test\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "enforcer_cached.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v3/persist/cache\"\n)\n\n// CachedEnforcer wraps Enforcer and provides decision cache.\ntype CachedEnforcer struct {\n\t*Enforcer\n\texpireTime  time.Duration\n\tcache       cache.Cache\n\tenableCache int32\n\tlocker      *sync.RWMutex\n}\n\ntype CacheableParam interface {\n\tGetCacheKey() string\n}\n\n// NewCachedEnforcer creates a cached enforcer via file or DB.\nfunc NewCachedEnforcer(params ...interface{}) (*CachedEnforcer, error) {\n\te := &CachedEnforcer{}\n\tvar err error\n\te.Enforcer, err = NewEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te.enableCache = 1\n\te.cache, _ = cache.NewDefaultCache()\n\te.locker = new(sync.RWMutex)\n\treturn e, nil\n}\n\n// EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.\nfunc (e *CachedEnforcer) EnableCache(enableCache bool) {\n\tvar enabled int32\n\tif enableCache {\n\t\tenabled = 1\n\t}\n\tatomic.StoreInt32(&e.enableCache, enabled)\n}\n\n// Enforce decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).\n// if rvals is not string , ignore the cache.\nfunc (e *CachedEnforcer) Enforce(rvals ...interface{}) (bool, error) {\n\tif atomic.LoadInt32(&e.enableCache) == 0 {\n\t\treturn e.Enforcer.Enforce(rvals...)\n\t}\n\n\tkey, ok := e.getKey(rvals...)\n\tif !ok {\n\t\treturn e.Enforcer.Enforce(rvals...)\n\t}\n\n\tif res, err := e.getCachedResult(key); err == nil {\n\t\treturn res, nil\n\t} else if err != cache.ErrNoSuchKey {\n\t\treturn res, err\n\t}\n\n\tres, err := e.Enforcer.Enforce(rvals...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\terr = e.setCachedResult(key, res, e.expireTime)\n\treturn res, err\n}\n\nfunc (e *CachedEnforcer) LoadPolicy() error {\n\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\tif err := e.cache.Clear(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn e.Enforcer.LoadPolicy()\n}\n\nfunc (e *CachedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {\n\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\tkey, ok := e.getKey(params...)\n\t\tif ok {\n\t\t\tif err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\treturn e.Enforcer.RemovePolicy(params...)\n}\n\nfunc (e *CachedEnforcer) RemovePolicies(rules [][]string) (bool, error) {\n\tif len(rules) != 0 {\n\t\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\t\tirule := make([]interface{}, len(rules[0]))\n\t\t\tfor _, rule := range rules {\n\t\t\t\tfor i, param := range rule {\n\t\t\t\t\tirule[i] = param\n\t\t\t\t}\n\t\t\t\tkey, _ := e.getKey(irule...)\n\t\t\t\tif err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn e.Enforcer.RemovePolicies(rules)\n}\n\nfunc (e *CachedEnforcer) getCachedResult(key string) (res bool, err error) {\n\te.locker.Lock()\n\tdefer e.locker.Unlock()\n\treturn e.cache.Get(key)\n}\n\nfunc (e *CachedEnforcer) SetExpireTime(expireTime time.Duration) {\n\te.expireTime = expireTime\n}\n\nfunc (e *CachedEnforcer) SetCache(c cache.Cache) {\n\te.cache = c\n}\n\nfunc (e *CachedEnforcer) setCachedResult(key string, res bool, extra ...interface{}) error {\n\te.locker.Lock()\n\tdefer e.locker.Unlock()\n\treturn e.cache.Set(key, res, extra...)\n}\n\nfunc (e *CachedEnforcer) getKey(params ...interface{}) (string, bool) {\n\treturn GetCacheKey(params...)\n}\n\n// InvalidateCache deletes all the existing cached decisions.\nfunc (e *CachedEnforcer) InvalidateCache() error {\n\te.locker.Lock()\n\tdefer e.locker.Unlock()\n\treturn e.cache.Clear()\n}\n\nfunc GetCacheKey(params ...interface{}) (string, bool) {\n\tkey := strings.Builder{}\n\tfor _, param := range params {\n\t\tswitch typedParam := param.(type) {\n\t\tcase string:\n\t\t\tkey.WriteString(typedParam)\n\t\tcase CacheableParam:\n\t\t\tkey.WriteString(typedParam.GetCacheKey())\n\t\tdefault:\n\t\t\treturn \"\", false\n\t\t}\n\t\tkey.WriteString(\"$$\")\n\t}\n\treturn key.String(), true\n}\n\n// ClearPolicy clears all policy.\nfunc (e *CachedEnforcer) ClearPolicy() {\n\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\tif err := e.cache.Clear(); err != nil {\n\t\t\t// Logger has been removed - error is ignored\n\t\t\treturn\n\t\t}\n\t}\n\te.Enforcer.ClearPolicy()\n}\n"
  },
  {
    "path": "enforcer_cached_b_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc BenchmarkCachedRaw(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\trawEnforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedBasicModel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data2\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelSmall(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_model.conf\")\n\t// 100 roles, 10 resources.\n\tfor i := 0; i < 100; i++ {\n\t\t_, err := e.AddPolicy(fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n\t// 1000 users.\n\tfor i := 0; i < 1000; i++ {\n\t\t_, err := e.AddGroupingPolicy(fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10))\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user501\", \"data9\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelMedium(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_model.conf\")\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 10000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user5001\", \"data150\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelLarge(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 100000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 100000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user50001\", \"data1500\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelWithResourceRoles(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_with_resource_roles_model.conf\", \"examples/rbac_with_resource_roles_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelWithDomains(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"domain1\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedABACModel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/abac_model.conf\")\n\tdata1 := newTestResource(\"data1\", \"alice\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", data1, \"read\")\n\t}\n}\n\nfunc BenchmarkCachedKeyMatchModel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/keymatch_model.conf\", \"examples/keymatch_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"/alice_data/resource1\", \"GET\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelWithDeny(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_with_deny_model.conf\", \"examples/rbac_with_deny_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedPriorityModel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/priority_model.conf\", \"examples/priority_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkCachedWithEnforceContext(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/priority_model_enforce_context.conf\", \"examples/priority_policy_enforce_context.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(EnforceContext{RType: \"r2\", PType: \"p\", EType: \"e\", MType: \"m2\"}, \"alice\", \"data1\")\n\t}\n}\n\nfunc BenchmarkCachedRBACModelMediumParallel(b *testing.B) {\n\te, _ := NewCachedEnforcer(\"examples/rbac_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 100000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 100000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\t_, _ = e.Enforce(\"user5001\", \"data150\", \"read\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "enforcer_cached_gfunction_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport \"testing\"\n\n// TestCachedGFunctionAfterAddingGroupingPolicy tests that adding new grouping policies\n// at runtime correctly updates permission evaluation without requiring a restart or manual cache clearing.\n// This is a regression test for the issue where GenerateGFunction's memoization cache\n// caused stale permission results after adding new grouping policies.\nfunc TestCachedGFunctionAfterAddingGroupingPolicy(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Initial state: bob should not have access to data2 (read)\n\t// bob has no roles initially\n\tok, err := e.Enforce(\"bob\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t}\n\tif ok {\n\t\tt.Error(\"bob should not have read access to data2 initially\")\n\t}\n\n\t// Add a new grouping policy: bob becomes data2_admin\n\t// data2_admin has read and write access to data2\n\t_, err = e.AddGroupingPolicy(\"bob\", \"data2_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add grouping policy: %v\", err)\n\t}\n\n\t// Now bob should have read access to data2 through the data2_admin role\n\t// This should work immediately without needing to clear cache or restart\n\tok, err = e.Enforce(\"bob\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed after adding grouping policy: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"bob should have read access to data2 after being added to data2_admin role\")\n\t}\n\n\t// Also verify write access works\n\tok, err = e.Enforce(\"bob\", \"data2\", \"write\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"bob should have write access to data2 after being added to data2_admin role\")\n\t}\n}\n\n// TestCachedGFunctionWithMultipleEnforceCalls tests that the g() function cache\n// properly invalidates when grouping policies change, even after multiple enforce calls.\nfunc TestCachedGFunctionWithMultipleEnforceCalls(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Make multiple enforce calls to ensure the g() function closure is cached\n\tfor i := 0; i < 5; i++ {\n\t\tok, enforceErr := e.Enforce(\"charlie\", \"data2\", \"read\")\n\t\tif enforceErr != nil {\n\t\t\tt.Fatalf(\"Enforce failed on iteration %d: %v\", i, enforceErr)\n\t\t}\n\t\tif ok {\n\t\t\tt.Errorf(\"charlie should not have read access to data2 on iteration %d\", i)\n\t\t}\n\t}\n\n\t// Add grouping policy\n\t_, err = e.AddGroupingPolicy(\"charlie\", \"data2_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add grouping policy: %v\", err)\n\t}\n\n\t// Immediately verify the change took effect\n\tok, err := e.Enforce(\"charlie\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed after adding grouping policy: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"charlie should have read access to data2 immediately after being added to data2_admin role\")\n\t}\n\n\t// Make multiple calls to ensure it stays consistent\n\tfor i := 0; i < 5; i++ {\n\t\tok, enforceErr := e.Enforce(\"charlie\", \"data2\", \"read\")\n\t\tif enforceErr != nil {\n\t\t\tt.Fatalf(\"Enforce failed on iteration %d after policy change: %v\", i, enforceErr)\n\t\t}\n\t\tif !ok {\n\t\t\tt.Errorf(\"charlie should have read access to data2 on iteration %d after policy change\", i)\n\t\t}\n\t}\n}\n\n// TestCachedGFunctionAfterRemovingGroupingPolicy tests that removing grouping policies\n// also properly invalidates the g() function cache.\nfunc TestCachedGFunctionAfterRemovingGroupingPolicy(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// alice initially has the data2_admin role\n\tok, err := e.Enforce(\"alice\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"alice should have read access to data2 initially\")\n\t}\n\n\t// Remove alice from data2_admin role\n\t_, err = e.RemoveGroupingPolicy(\"alice\", \"data2_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to remove grouping policy: %v\", err)\n\t}\n\n\t// Now alice should not have access to data2 (she only has access to data1)\n\tok, err = e.Enforce(\"alice\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed after removing grouping policy: %v\", err)\n\t}\n\tif ok {\n\t\tt.Error(\"alice should not have read access to data2 after being removed from data2_admin role\")\n\t}\n\n\t// Verify alice still has access to data1 (direct policy)\n\tok, err = e.Enforce(\"alice\", \"data1\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"alice should still have read access to data1 (direct policy)\")\n\t}\n}\n\n// TestCachedGFunctionAfterBuildRoleLinks tests the specific scenario mentioned in the bug report:\n// adding grouping policies and calling BuildRoleLinks() manually should properly invalidate the cache.\nfunc TestCachedGFunctionAfterBuildRoleLinks(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// First, make some enforce calls to ensure the g() function closure is created and cached\n\t// This will cache \"bob\" NOT having data2_admin role in the g() function's sync.Map\n\tfor i := 0; i < 3; i++ {\n\t\tok, enforceErr := e.Enforce(\"bob\", \"data2\", \"read\")\n\t\tif enforceErr != nil {\n\t\t\tt.Fatalf(\"Enforce failed on iteration %d: %v\", i, enforceErr)\n\t\t}\n\t\tif ok {\n\t\t\tt.Errorf(\"bob should not have read access to data2 on iteration %d (before adding role)\", i)\n\t\t}\n\t}\n\n\t// Disable autoBuildRoleLinks to manually control when role links are rebuilt\n\te.EnableAutoBuildRoleLinks(false)\n\n\t// Manually add the grouping policy to the model (bypassing BuildIncrementalRoleLinks)\n\t// This simulates the scenario where policies are loaded from database\n\terr = e.model.AddPolicy(\"g\", \"g\", []string{\"bob\", \"data2_admin\"})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add grouping policy to model: %v\", err)\n\t}\n\n\t// Manually build role links as mentioned in the issue\n\t// This is the key part - BuildRoleLinks() should invalidate the matcher map cache\n\terr = e.BuildRoleLinks()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to build role links: %v\", err)\n\t}\n\n\t// Now bob should have read access to data2 through the data2_admin role\n\t// This is where the bug would manifest - if BuildRoleLinks() doesn't invalidate the cache,\n\t// the old g() function closure with \"bob->data2_admin = false\" cached will still be used\n\tok, err := e.Enforce(\"bob\", \"data2\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed after BuildRoleLinks: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Error(\"bob should have read access to data2 after BuildRoleLinks() - this indicates the g() function cache was not properly invalidated\")\n\t}\n}\n"
  },
  {
    "path": "enforcer_cached_synced.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v3/persist/cache\"\n)\n\n// SyncedCachedEnforcer wraps Enforcer and provides decision sync cache.\ntype SyncedCachedEnforcer struct {\n\t*SyncedEnforcer\n\texpireTime  time.Duration\n\tcache       cache.Cache\n\tenableCache int32\n\tlocker      *sync.RWMutex\n}\n\n// NewSyncedCachedEnforcer creates a sync cached enforcer via file or DB.\nfunc NewSyncedCachedEnforcer(params ...interface{}) (*SyncedCachedEnforcer, error) {\n\te := &SyncedCachedEnforcer{}\n\tvar err error\n\te.SyncedEnforcer, err = NewSyncedEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te.enableCache = 1\n\te.cache, _ = cache.NewSyncCache()\n\te.locker = new(sync.RWMutex)\n\treturn e, nil\n}\n\n// EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.\nfunc (e *SyncedCachedEnforcer) EnableCache(enableCache bool) {\n\tvar enabled int32\n\tif enableCache {\n\t\tenabled = 1\n\t}\n\tatomic.StoreInt32(&e.enableCache, enabled)\n}\n\n// Enforce decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).\n// if rvals is not string , ignore the cache.\nfunc (e *SyncedCachedEnforcer) Enforce(rvals ...interface{}) (bool, error) {\n\tif atomic.LoadInt32(&e.enableCache) == 0 {\n\t\treturn e.SyncedEnforcer.Enforce(rvals...)\n\t}\n\n\tkey, ok := e.getKey(rvals...)\n\tif !ok {\n\t\treturn e.SyncedEnforcer.Enforce(rvals...)\n\t}\n\n\tif res, err := e.getCachedResult(key); err == nil {\n\t\treturn res, nil\n\t} else if err != cache.ErrNoSuchKey {\n\t\treturn res, err\n\t}\n\n\tres, err := e.SyncedEnforcer.Enforce(rvals...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\terr = e.setCachedResult(key, res, e.expireTime)\n\treturn res, err\n}\n\nfunc (e *SyncedCachedEnforcer) LoadPolicy() error {\n\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\tif err := e.cache.Clear(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn e.SyncedEnforcer.LoadPolicy()\n}\n\nfunc (e *SyncedCachedEnforcer) AddPolicy(params ...interface{}) (bool, error) {\n\tif ok, err := e.checkOneAndRemoveCache(params...); !ok {\n\t\treturn ok, err\n\t}\n\treturn e.SyncedEnforcer.AddPolicy(params...)\n}\n\nfunc (e *SyncedCachedEnforcer) AddPolicies(rules [][]string) (bool, error) {\n\tif ok, err := e.checkManyAndRemoveCache(rules); !ok {\n\t\treturn ok, err\n\t}\n\treturn e.SyncedEnforcer.AddPolicies(rules)\n}\n\nfunc (e *SyncedCachedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {\n\tif ok, err := e.checkOneAndRemoveCache(params...); !ok {\n\t\treturn ok, err\n\t}\n\treturn e.SyncedEnforcer.RemovePolicy(params...)\n}\n\nfunc (e *SyncedCachedEnforcer) RemovePolicies(rules [][]string) (bool, error) {\n\tif ok, err := e.checkManyAndRemoveCache(rules); !ok {\n\t\treturn ok, err\n\t}\n\treturn e.SyncedEnforcer.RemovePolicies(rules)\n}\n\nfunc (e *SyncedCachedEnforcer) getCachedResult(key string) (res bool, err error) {\n\treturn e.cache.Get(key)\n}\n\nfunc (e *SyncedCachedEnforcer) SetExpireTime(expireTime time.Duration) {\n\te.locker.Lock()\n\tdefer e.locker.Unlock()\n\te.expireTime = expireTime\n}\n\n// SetCache need to be sync cache.\nfunc (e *SyncedCachedEnforcer) SetCache(c cache.Cache) {\n\te.locker.Lock()\n\tdefer e.locker.Unlock()\n\te.cache = c\n}\n\nfunc (e *SyncedCachedEnforcer) setCachedResult(key string, res bool, extra ...interface{}) error {\n\treturn e.cache.Set(key, res, extra...)\n}\n\nfunc (e *SyncedCachedEnforcer) getKey(params ...interface{}) (string, bool) {\n\treturn GetCacheKey(params...)\n}\n\n// InvalidateCache deletes all the existing cached decisions.\nfunc (e *SyncedCachedEnforcer) InvalidateCache() error {\n\treturn e.cache.Clear()\n}\n\nfunc (e *SyncedCachedEnforcer) checkOneAndRemoveCache(params ...interface{}) (bool, error) {\n\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\tkey, ok := e.getKey(params...)\n\t\tif ok {\n\t\t\tif err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\treturn true, nil\n}\n\nfunc (e *SyncedCachedEnforcer) checkManyAndRemoveCache(rules [][]string) (bool, error) {\n\tif len(rules) != 0 {\n\t\tif atomic.LoadInt32(&e.enableCache) != 0 {\n\t\t\tirule := make([]interface{}, len(rules[0]))\n\t\t\tfor _, rule := range rules {\n\t\t\t\tfor i, param := range rule {\n\t\t\t\t\tirule[i] = param\n\t\t\t\t}\n\t\t\t\tkey, _ := e.getKey(irule...)\n\t\t\t\tif err := e.cache.Delete(key); err != nil && err != cache.ErrNoSuchKey {\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "enforcer_cached_synced_test.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc testSyncEnforceCache(t *testing.T, e *SyncedCachedEnforcer, sub string, obj interface{}, act string, res bool) {\n\tt.Helper()\n\tif myRes, _ := e.Enforce(sub, obj, act); myRes != res {\n\t\tt.Errorf(\"%s, %v, %s: %t, supposed to be %t\", sub, obj, act, myRes, res)\n\t}\n}\n\nfunc TestSyncCache(t *testing.T) {\n\te, _ := NewSyncedCachedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\te.expireTime = time.Millisecond\n\t// The cache is enabled by default for NewCachedEnforcer.\n\tg := sync.WaitGroup{}\n\tgoThread := 1000\n\tg.Add(goThread)\n\tfor i := 0; i < goThread; i++ {\n\t\tgo func() {\n\t\t\t_, _ = e.AddPolicy(\"alice\", \"data2\", \"read\")\n\t\t\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\t\t\tif e.InvalidateCache() != nil {\n\t\t\t\tpanic(\"never reached\")\n\t\t\t}\n\t\t\tg.Done()\n\t\t}()\n\t}\n\tg.Wait()\n\t_, _ = e.RemovePolicy(\"alice\", \"data2\", \"read\")\n\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\ttime.Sleep(time.Millisecond * 2) // coverage for expire\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"write\", false)\n\t// The cache is enabled, calling RemovePolicy, LoadPolicy or RemovePolicies will\n\t// also operate cached items.\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"write\", false)\n\n\te, _ = NewSyncedCachedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestSyncEnforceCache(t, e, \"bob\", \"data2\", \"write\", true)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"write\", true)\n\n\t_, _ = e.RemovePolicies([][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t})\n\n\ttestSyncEnforceCache(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestSyncEnforceCache(t, e, \"bob\", \"data2\", \"write\", false)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestSyncEnforceCache(t, e, \"alice\", \"data2\", \"write\", true)\n}\n"
  },
  {
    "path": "enforcer_cached_test.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport \"testing\"\n\nfunc testEnforceCache(t *testing.T, e *CachedEnforcer, sub string, obj interface{}, act string, res bool) {\n\tt.Helper()\n\tif myRes, _ := e.Enforce(sub, obj, act); myRes != res {\n\t\tt.Errorf(\"%s, %v, %s: %t, supposed to be %t\", sub, obj, act, myRes, res)\n\t}\n}\n\nfunc TestCache(t *testing.T) {\n\te, _ := NewCachedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t// The cache is enabled by default for NewCachedEnforcer.\n\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", false)\n\n\t// The cache is enabled, calling RemovePolicy, LoadPolicy or RemovePolicies will\n\t// also operate cached items.\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", false)\n\n\te, _ = NewCachedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforceCache(t, e, \"bob\", \"data2\", \"write\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", true)\n\n\t_, _ = e.RemovePolicies([][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t})\n\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforceCache(t, e, \"bob\", \"data2\", \"write\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", true)\n\n\te, _ = NewCachedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforceCache(t, e, \"bob\", \"data2\", \"write\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", true)\n\n\te.ClearPolicy()\n\n\ttestEnforceCache(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforceCache(t, e, \"bob\", \"data2\", \"write\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforceCache(t, e, \"alice\", \"data2\", \"write\", false)\n}\n"
  },
  {
    "path": "enforcer_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\tErr \"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// ContextEnforcer wraps Enforcer and provides context-aware operations.\ntype ContextEnforcer struct {\n\t*Enforcer\n\tadapterCtx persist.ContextAdapter\n}\n\n// NewContextEnforcer creates a context-aware enforcer via file or DB.\nfunc NewContextEnforcer(params ...interface{}) (IEnforcerContext, error) {\n\te := &ContextEnforcer{}\n\tvar err error\n\te.Enforcer, err = NewEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif e.Enforcer.adapter != nil {\n\t\tif contextAdapter, ok := e.Enforcer.adapter.(persist.ContextAdapter); ok {\n\t\t\te.adapterCtx = contextAdapter\n\t\t} else {\n\t\t\treturn nil, errors.New(\"adapter does not support context operations, ContextAdapter interface not implemented\")\n\t\t}\n\t} else {\n\t\treturn nil, errors.New(\"no adapter provided, ContextEnforcer requires a ContextAdapter\")\n\t}\n\n\treturn e, nil\n}\n\n// LoadPolicyCtx loads all policy rules from the storage with context.\nfunc (e *ContextEnforcer) LoadPolicyCtx(ctx context.Context) error {\n\tnewModel, err := e.loadPolicyFromAdapterCtx(ctx, e.model)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = e.applyModifiedModel(newModel)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (e *ContextEnforcer) loadPolicyFromAdapterCtx(ctx context.Context, baseModel model.Model) (model.Model, error) {\n\tnewModel := baseModel.Copy()\n\tnewModel.ClearPolicy()\n\n\tif err := e.adapterCtx.LoadPolicyCtx(ctx, newModel); err != nil && err.Error() != \"invalid file path, file path cannot be empty\" {\n\t\treturn nil, err\n\t}\n\n\tif err := newModel.SortPoliciesBySubjectHierarchy(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := newModel.SortPoliciesByPriority(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newModel, nil\n}\n\n// LoadFilteredPolicyCtx loads all policy rules from the storage with context and filter.\nfunc (e *Enforcer) LoadFilteredPolicyCtx(ctx context.Context, filter interface{}) error {\n\te.model.ClearPolicy()\n\treturn e.loadFilteredPolicyCtx(ctx, filter)\n}\n\n// LoadIncrementalFilteredPolicyCtx append a filtered policy from file/database with context.\nfunc (e *Enforcer) LoadIncrementalFilteredPolicyCtx(ctx context.Context, filter interface{}) error {\n\treturn e.loadFilteredPolicyCtx(ctx, filter)\n}\n\nfunc (e *Enforcer) loadFilteredPolicyCtx(ctx context.Context, filter interface{}) error {\n\te.invalidateMatcherMap()\n\n\tvar filteredAdapter persist.ContextFilteredAdapter\n\n\t// Attempt to cast the Adapter as a FilteredAdapter\n\tswitch adapter := e.adapter.(type) {\n\tcase persist.ContextFilteredAdapter:\n\t\tfilteredAdapter = adapter\n\tdefault:\n\t\treturn errors.New(\"filtered policies are not supported by this adapter\")\n\t}\n\tif err := filteredAdapter.LoadFilteredPolicyCtx(ctx, e.model, filter); err != nil && err.Error() != \"invalid file path, file path cannot be empty\" {\n\t\treturn err\n\t}\n\n\tif err := e.model.SortPoliciesBySubjectHierarchy(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := e.model.SortPoliciesByPriority(); err != nil {\n\t\treturn err\n\t}\n\n\te.initRmMap()\n\te.model.PrintPolicy()\n\tif e.autoBuildRoleLinks {\n\t\terr := e.BuildRoleLinks()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// IsFilteredCtx returns true if the loaded policy has been filtered with context.\nfunc (e *ContextEnforcer) IsFilteredCtx(ctx context.Context) bool {\n\tif adapter, ok := e.adapter.(persist.ContextFilteredAdapter); ok {\n\t\treturn adapter.IsFilteredCtx(ctx)\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc (e *ContextEnforcer) SavePolicyCtx(ctx context.Context) error {\n\tif e.IsFiltered() {\n\t\treturn errors.New(\"cannot save a filtered policy\")\n\t}\n\tif err := e.adapterCtx.SavePolicyCtx(ctx, e.model); err != nil {\n\t\treturn err\n\t}\n\tif e.watcher != nil {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForSavePolicy(e.model)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// AddPolicyCtx adds a policy rule to the storage with context.\nfunc (e *ContextEnforcer) AddPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {\n\treturn e.AddNamedPolicyCtx(ctx, \"p\", params...)\n}\n\n// AddPoliciesCtx adds policy rules to the storage with context.\nfunc (e *ContextEnforcer) AddPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.AddNamedPoliciesCtx(ctx, \"p\", rules)\n}\n\n// AddNamedPolicyCtx adds a named policy rule to the storage with context.\nfunc (e *ContextEnforcer) AddNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\tstrSlice = append(make([]string, 0, len(strSlice)), strSlice...)\n\t\treturn e.addPolicyCtx(ctx, \"p\", ptype, strSlice)\n\t}\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.addPolicyCtx(ctx, \"p\", ptype, policy)\n}\n\n// AddNamedPoliciesCtx adds named policy rules to the storage with context.\nfunc (e *ContextEnforcer) AddNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesCtx(ctx, \"p\", ptype, rules, false)\n}\n\nfunc (e *ContextEnforcer) AddPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.AddNamedPoliciesExCtx(ctx, \"p\", rules)\n}\n\nfunc (e *ContextEnforcer) AddNamedPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesCtx(ctx, \"p\", ptype, rules, true)\n}\n\n// RemovePolicyCtx removes a policy rule from the storage with context.\nfunc (e *ContextEnforcer) RemovePolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {\n\treturn e.RemoveNamedPolicyCtx(ctx, \"p\", params...)\n}\n\n// RemoveNamedPolicyCtx removes a named policy rule from the storage with context.\nfunc (e *ContextEnforcer) RemoveNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\treturn e.removePolicyCtx(ctx, \"p\", ptype, strSlice)\n\t}\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.removePolicyCtx(ctx, \"p\", ptype, policy)\n}\n\n// RemovePoliciesCtx removes policy rules from the storage with context.\nfunc (e *ContextEnforcer) RemovePoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.RemoveNamedPoliciesCtx(ctx, \"p\", rules)\n}\n\n// RemoveNamedPoliciesCtx removes named policy rules from the storage with context.\nfunc (e *ContextEnforcer) RemoveNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.removePoliciesCtx(ctx, \"p\", ptype, rules)\n}\n\n// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.\nfunc (e *ContextEnforcer) RemoveFilteredPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.RemoveFilteredNamedPolicyCtx(ctx, \"p\", fieldIndex, fieldValues...)\n}\n\n// RemoveFilteredNamedPolicyCtx removes named policy rules that match the filter from the storage with context.\nfunc (e *ContextEnforcer) RemoveFilteredNamedPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicyCtx(ctx, \"p\", ptype, fieldIndex, fieldValues)\n}\n\n// UpdatePolicyCtx updates a policy rule in the storage with context.\nfunc (e *ContextEnforcer) UpdatePolicyCtx(ctx context.Context, oldPolicy []string, newPolicy []string) (bool, error) {\n\treturn e.UpdateNamedPolicyCtx(ctx, \"p\", oldPolicy, newPolicy)\n}\n\n// UpdateNamedPolicyCtx updates a named policy rule in the storage with context.\nfunc (e *ContextEnforcer) UpdateNamedPolicyCtx(ctx context.Context, ptype string, p1 []string, p2 []string) (bool, error) {\n\treturn e.updatePolicyCtx(ctx, \"p\", ptype, p1, p2)\n}\n\n// UpdatePoliciesCtx updates policy rules in the storage with context.\nfunc (e *ContextEnforcer) UpdatePoliciesCtx(ctx context.Context, oldPolicies [][]string, newPolicies [][]string) (bool, error) {\n\treturn e.UpdateNamedPoliciesCtx(ctx, \"p\", oldPolicies, newPolicies)\n}\n\n// UpdateNamedPoliciesCtx updates named policy rules in the storage with context.\nfunc (e *ContextEnforcer) UpdateNamedPoliciesCtx(ctx context.Context, ptype string, p1 [][]string, p2 [][]string) (bool, error) {\n\treturn e.updatePoliciesCtx(ctx, \"p\", ptype, p1, p2)\n}\n\n// UpdateFilteredPoliciesCtx updates policy rules that match the filter in the storage with context.\nfunc (e *ContextEnforcer) UpdateFilteredPoliciesCtx(ctx context.Context, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.UpdateFilteredNamedPoliciesCtx(ctx, \"p\", newPolicies, fieldIndex, fieldValues...)\n}\n\n// UpdateFilteredNamedPoliciesCtx updates named policy rules that match the filter in the storage with context.\nfunc (e *ContextEnforcer) UpdateFilteredNamedPoliciesCtx(ctx context.Context, ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.updateFilteredPoliciesCtx(ctx, \"p\", ptype, newPolicies, fieldIndex, fieldValues...)\n}\n\n// Grouping Policy Context Methods\n\n// AddGroupingPolicyCtx adds a grouping policy rule to the storage with context.\nfunc (e *ContextEnforcer) AddGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {\n\treturn e.AddNamedGroupingPolicyCtx(ctx, \"g\", params...)\n}\n\n// AddGroupingPoliciesCtx adds grouping policy rules to the storage with context.\nfunc (e *ContextEnforcer) AddGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.AddNamedGroupingPoliciesCtx(ctx, \"g\", rules)\n}\n\nfunc (e *ContextEnforcer) AddGroupingPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.AddNamedGroupingPoliciesExCtx(ctx, \"g\", rules)\n}\n\n// AddNamedGroupingPolicyCtx adds a named grouping policy rule to the storage with context.\nfunc (e *ContextEnforcer) AddNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {\n\tvar ruleAdded bool\n\tvar err error\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\truleAdded, err = e.addPolicyCtx(ctx, \"g\", ptype, strSlice)\n\t} else {\n\t\tpolicy := make([]string, 0)\n\t\tfor _, param := range params {\n\t\t\tpolicy = append(policy, param.(string))\n\t\t}\n\t\truleAdded, err = e.addPolicyCtx(ctx, \"g\", ptype, policy)\n\t}\n\n\treturn ruleAdded, err\n}\n\n// AddNamedGroupingPoliciesCtx adds named grouping policy rules to the storage with context.\nfunc (e *ContextEnforcer) AddNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesCtx(ctx, \"g\", ptype, rules, false)\n}\n\nfunc (e *ContextEnforcer) AddNamedGroupingPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesCtx(ctx, \"g\", ptype, rules, true)\n}\n\n// RemoveGroupingPolicyCtx removes a grouping policy rule from the storage with context.\nfunc (e *ContextEnforcer) RemoveGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error) {\n\treturn e.RemoveNamedGroupingPolicyCtx(ctx, \"g\", params...)\n}\n\n// RemoveNamedGroupingPolicyCtx removes a named grouping policy rule from the storage with context.\nfunc (e *ContextEnforcer) RemoveNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error) {\n\tvar ruleRemoved bool\n\tvar err error\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\truleRemoved, err = e.removePolicyCtx(ctx, \"g\", ptype, strSlice)\n\t} else {\n\t\tpolicy := make([]string, 0)\n\t\tfor _, param := range params {\n\t\t\tpolicy = append(policy, param.(string))\n\t\t}\n\n\t\truleRemoved, err = e.removePolicyCtx(ctx, \"g\", ptype, policy)\n\t}\n\n\treturn ruleRemoved, err\n}\n\n// RemoveGroupingPoliciesCtx removes grouping policy rules from the storage with context.\nfunc (e *ContextEnforcer) RemoveGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error) {\n\treturn e.RemoveNamedGroupingPoliciesCtx(ctx, \"g\", rules)\n}\n\n// RemoveNamedGroupingPoliciesCtx removes named grouping policy rules from the storage with context.\nfunc (e *ContextEnforcer) RemoveNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error) {\n\treturn e.removePoliciesCtx(ctx, \"g\", ptype, rules)\n}\n\n// RemoveFilteredGroupingPolicyCtx removes grouping policy rules that match the filter from the storage with context.\nfunc (e *ContextEnforcer) RemoveFilteredGroupingPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.RemoveFilteredNamedGroupingPolicyCtx(ctx, \"g\", fieldIndex, fieldValues...)\n}\n\n// RemoveFilteredNamedGroupingPolicyCtx removes named grouping policy rules that match the filter from the storage with context.\nfunc (e *ContextEnforcer) RemoveFilteredNamedGroupingPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicyCtx(ctx, \"g\", ptype, fieldIndex, fieldValues)\n}\n\n// UpdateGroupingPolicyCtx updates a grouping policy rule in the storage with context.\nfunc (e *ContextEnforcer) UpdateGroupingPolicyCtx(ctx context.Context, oldRule []string, newRule []string) (bool, error) {\n\treturn e.UpdateNamedGroupingPolicyCtx(ctx, \"g\", oldRule, newRule)\n}\n\n// UpdateNamedGroupingPolicyCtx updates a named grouping policy rule in the storage with context.\nfunc (e *ContextEnforcer) UpdateNamedGroupingPolicyCtx(ctx context.Context, ptype string, oldRule []string, newRule []string) (bool, error) {\n\treturn e.updatePolicyCtx(ctx, \"g\", ptype, oldRule, newRule)\n}\n\n// UpdateGroupingPoliciesCtx updates grouping policy rules in the storage with context.\nfunc (e *ContextEnforcer) UpdateGroupingPoliciesCtx(ctx context.Context, oldRules [][]string, newRules [][]string) (bool, error) {\n\treturn e.UpdateNamedGroupingPoliciesCtx(ctx, \"g\", oldRules, newRules)\n}\n\n// UpdateNamedGroupingPoliciesCtx updates named grouping policy rules in the storage with context.\nfunc (e *ContextEnforcer) UpdateNamedGroupingPoliciesCtx(ctx context.Context, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\treturn e.updatePoliciesCtx(ctx, \"g\", ptype, oldRules, newRules)\n}\n\n// Self Context Methods (bypass watcher notifications)\n\n// SelfAddPolicyCtx adds a policy rule to the current policy with context.\nfunc (e *ContextEnforcer) SelfAddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\treturn e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)\n}\n\n// SelfAddPoliciesCtx adds policy rules to the current policy with context.\nfunc (e *ContextEnforcer) SelfAddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, false)\n}\n\nfunc (e *ContextEnforcer) SelfAddPoliciesExCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, true)\n}\n\n// SelfRemovePolicyCtx removes a policy rule from the current policy with context.\nfunc (e *ContextEnforcer) SelfRemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\treturn e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)\n}\n\n// SelfRemovePoliciesCtx removes policy rules from the current policy with context.\nfunc (e *ContextEnforcer) SelfRemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)\n}\n\n// SelfRemoveFilteredPolicyCtx removes policy rules that match the filter from the current policy with context.\nfunc (e *ContextEnforcer) SelfRemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype, fieldIndex, fieldValues)\n}\n\n// SelfUpdatePolicyCtx updates a policy rule in the current policy with context.\nfunc (e *ContextEnforcer) SelfUpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error) {\n\treturn e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule, newRule)\n}\n\n// SelfUpdatePoliciesCtx updates policy rules in the current policy with context.\nfunc (e *ContextEnforcer) SelfUpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) (bool, error) {\n\treturn e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules, newRules)\n}\n\n// Internal API methods with context support\n\n// addPolicyWithoutNotifyCtx adds a rule to the current policy with context.\nfunc (e *ContextEnforcer) addPolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})\n\t}\n\n\thasPolicy, err := e.model.HasPolicy(sec, ptype, rule)\n\tif hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err = e.adapterCtx.AddPolicyCtx(ctx, sec, ptype, rule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\terr = e.model.AddPolicy(sec, ptype, rule)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// addPoliciesWithoutNotifyCtx adds rules to the current policy with context.\nfunc (e *ContextEnforcer) addPoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.AddPolicies(sec, ptype, rules)\n\t}\n\n\tif !autoRemoveRepeat {\n\t\thasPolicies, err := e.model.HasPolicies(sec, ptype, rules)\n\t\tif hasPolicies || err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.(persist.ContextBatchAdapter).AddPoliciesCtx(ctx, sec, ptype, rules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\terr := e.model.AddPolicies(sec, ptype, rules)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\n\t\terr = e.BuildIncrementalConditionalRoleLinks(model.PolicyAdd, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// removePolicyWithoutNotifyCtx removes a rule from the current policy with context.\nfunc (e *ContextEnforcer) removePolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemovePolicies(sec, ptype, [][]string{rule})\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.RemovePolicyCtx(ctx, sec, ptype, rule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleRemoved, err := e.model.RemovePolicy(sec, ptype, rule)\n\tif !ruleRemoved || err != nil {\n\t\treturn ruleRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{rule})\n\t\tif err != nil {\n\t\t\treturn ruleRemoved, err\n\t\t}\n\t}\n\n\treturn ruleRemoved, nil\n}\n\n// removePoliciesWithoutNotifyCtx removes rules from the current policy with context.\nfunc (e *ContextEnforcer) removePoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {\n\tif hasPolicies, err := e.model.HasPolicies(sec, ptype, rules); !hasPolicies || err != nil {\n\t\treturn hasPolicies, err\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemovePolicies(sec, ptype, rules)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.(persist.ContextBatchAdapter).RemovePoliciesCtx(ctx, sec, ptype, rules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\trulesRemoved, err := e.model.RemovePolicies(sec, ptype, rules)\n\tif !rulesRemoved || err != nil {\n\t\treturn rulesRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn rulesRemoved, err\n\t\t}\n\t}\n\treturn rulesRemoved, nil\n}\n\n// removeFilteredPolicyWithoutNotifyCtx removes policy rules that match the filter from the current policy with context.\nfunc (e *ContextEnforcer) removeFilteredPolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {\n\tif len(fieldValues) == 0 {\n\t\treturn false, Err.ErrInvalidFieldValuesParameter\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.RemoveFilteredPolicyCtx(ctx, sec, ptype, fieldIndex, fieldValues...); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleRemoved, effects, err := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\tif !ruleRemoved || err != nil {\n\t\treturn ruleRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, effects)\n\t\tif err != nil {\n\t\t\treturn ruleRemoved, err\n\t\t}\n\t}\n\n\treturn ruleRemoved, nil\n}\n\n// updatePolicyWithoutNotifyCtx updates a policy rule in the current policy with context.\nfunc (e *ContextEnforcer) updatePolicyWithoutNotifyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule, newRule)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePolicyCtx(ctx, sec, ptype, oldRule, newRule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\truleUpdated, err := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\nfunc (e *ContextEnforcer) updatePoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\tif len(newRules) != len(oldRules) {\n\t\treturn 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))\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules, newRules)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapterCtx.(persist.ContextUpdatableAdapter).UpdatePoliciesCtx(ctx, sec, ptype, oldRules, newRules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleUpdated, err := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\nfunc (e *ContextEnforcer) addPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\tok, err := e.addPolicyWithoutNotifyCtx(ctx, sec, ptype, rule)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForAddPolicy(sec, ptype, rule...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) addPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {\n\tok, err := e.addPoliciesWithoutNotifyCtx(ctx, sec, ptype, rules, autoRemoveRepeat)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForAddPolicies(sec, ptype, rules...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) updatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule []string, newRule []string) (bool, error) {\n\tok, err := e.updatePolicyWithoutNotifyCtx(ctx, sec, ptype, oldRule, newRule)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicy(sec, ptype, oldRule, newRule)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) updatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\tok, err := e.updatePoliciesWithoutNotifyCtx(ctx, sec, ptype, oldRules, newRules)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) removePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error) {\n\tok, err := e.removePolicyWithoutNotifyCtx(ctx, sec, ptype, rule)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForRemovePolicy(sec, ptype, rule...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) removePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error) {\n\tok, err := e.removePoliciesWithoutNotifyCtx(ctx, sec, ptype, rules)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForRemovePolicies(sec, ptype, rules...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\n// removeFilteredPolicy removes rules based on field filters from the current policy.\nfunc (e *ContextEnforcer) removeFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {\n\tok, err := e.removeFilteredPolicyWithoutNotifyCtx(ctx, sec, ptype, fieldIndex, fieldValues)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) updateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\toldRules, err := e.updateFilteredPoliciesWithoutNotifyCtx(ctx, sec, ptype, newRules, fieldIndex, fieldValues...)\n\tok := len(oldRules) != 0\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *ContextEnforcer) updateFilteredPoliciesWithoutNotifyCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\tvar (\n\t\toldRules [][]string\n\t\terr      error\n\t)\n\n\tif _, err = e.model.GetAssertion(sec, ptype); err != nil {\n\t\treturn oldRules, err\n\t}\n\n\tif e.shouldPersist() {\n\t\tif oldRules, err = e.adapter.(persist.ContextUpdatableAdapter).UpdateFilteredPoliciesCtx(ctx, sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\t// For compatibility, because some adapters return oldRules containing ptype, see https://github.com/casbin/xorm-adapter/issues/49\n\t\tfor i, oldRule := range oldRules {\n\t\t\tif len(oldRules[i]) == len(e.model[sec][ptype].Tokens)+1 {\n\t\t\t\toldRules[i] = oldRule[1:]\n\t\t\t}\n\t\t}\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn oldRules, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)\n\t}\n\n\truleChanged, err := e.model.RemovePolicies(sec, ptype, oldRules)\n\tif err != nil {\n\t\treturn oldRules, err\n\t}\n\terr = e.model.AddPolicies(sec, ptype, newRules)\n\tif err != nil {\n\t\treturn oldRules, err\n\t}\n\truleChanged = ruleChanged && len(newRules) != 0\n\tif !ruleChanged {\n\t\treturn make([][]string, 0), nil\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules\n\t\tif err != nil {\n\t\t\treturn oldRules, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules\n\t\tif err != nil {\n\t\t\treturn oldRules, err\n\t\t}\n\t}\n\n\treturn oldRules, nil\n}\n"
  },
  {
    "path": "enforcer_context_interface.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport \"context\"\n\ntype IEnforcerContext interface {\n\tIEnforcer\n\n\t/* Enforcer API */\n\tLoadPolicyCtx(ctx context.Context) error\n\tLoadFilteredPolicyCtx(ctx context.Context, filter interface{}) error\n\tLoadIncrementalFilteredPolicyCtx(ctx context.Context, filter interface{}) error\n\tIsFilteredCtx(ctx context.Context) bool\n\tSavePolicyCtx(ctx context.Context) error\n\n\t/* RBAC API */\n\tAddRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error)\n\tAddPermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error)\n\tAddPermissionsForUserCtx(ctx context.Context, user string, permissions ...[]string) (bool, error)\n\tDeletePermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error)\n\tDeletePermissionsForUserCtx(ctx context.Context, user string) (bool, error)\n\n\tDeleteRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error)\n\tDeleteRolesForUserCtx(ctx context.Context, user string, domain ...string) (bool, error)\n\tDeleteUserCtx(ctx context.Context, user string) (bool, error)\n\tDeleteRoleCtx(ctx context.Context, role string) (bool, error)\n\tDeletePermissionCtx(ctx context.Context, permission ...string) (bool, error)\n\n\t/* RBAC API with domains*/\n\tAddRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error)\n\tDeleteRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error)\n\tDeleteRolesForUserInDomainCtx(ctx context.Context, user string, domain string) (bool, error)\n\tDeleteAllUsersByDomainCtx(ctx context.Context, domain string) (bool, error)\n\tDeleteDomainsCtx(ctx context.Context, domains ...string) (bool, error)\n\n\t/* Management API */\n\tAddPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)\n\tAddPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)\n\tAddNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)\n\tAddNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\tAddPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error)\n\tAddNamedPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\n\tRemovePolicyCtx(ctx context.Context, params ...interface{}) (bool, error)\n\tRemovePoliciesCtx(ctx context.Context, rules [][]string) (bool, error)\n\tRemoveFilteredPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error)\n\tRemoveNamedPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)\n\tRemoveNamedPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\tRemoveFilteredNamedPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\n\tAddGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)\n\tAddGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)\n\tAddGroupingPoliciesExCtx(ctx context.Context, rules [][]string) (bool, error)\n\tAddNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)\n\tAddNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\tAddNamedGroupingPoliciesExCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\n\tRemoveGroupingPolicyCtx(ctx context.Context, params ...interface{}) (bool, error)\n\tRemoveGroupingPoliciesCtx(ctx context.Context, rules [][]string) (bool, error)\n\tRemoveFilteredGroupingPolicyCtx(ctx context.Context, fieldIndex int, fieldValues ...string) (bool, error)\n\tRemoveNamedGroupingPolicyCtx(ctx context.Context, ptype string, params ...interface{}) (bool, error)\n\tRemoveNamedGroupingPoliciesCtx(ctx context.Context, ptype string, rules [][]string) (bool, error)\n\tRemoveFilteredNamedGroupingPolicyCtx(ctx context.Context, ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\n\tUpdatePolicyCtx(ctx context.Context, oldPolicy []string, newPolicy []string) (bool, error)\n\tUpdatePoliciesCtx(ctx context.Context, oldPolicies [][]string, newPolicies [][]string) (bool, error)\n\tUpdateFilteredPoliciesCtx(ctx context.Context, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error)\n\n\tUpdateGroupingPolicyCtx(ctx context.Context, oldRule []string, newRule []string) (bool, error)\n\tUpdateGroupingPoliciesCtx(ctx context.Context, oldRules [][]string, newRules [][]string) (bool, error)\n\tUpdateNamedGroupingPolicyCtx(ctx context.Context, ptype string, oldRule []string, newRule []string) (bool, error)\n\tUpdateNamedGroupingPoliciesCtx(ctx context.Context, ptype string, oldRules [][]string, newRules [][]string) (bool, error)\n\n\t/* Management API with autoNotifyWatcher disabled */\n\tSelfAddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error)\n\tSelfAddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)\n\tSelfAddPoliciesExCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)\n\tSelfRemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) (bool, error)\n\tSelfRemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) (bool, error)\n\tSelfRemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\tSelfUpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) (bool, error)\n\tSelfUpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) (bool, error)\n}\n"
  },
  {
    "path": "enforcer_context_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestIEnforcerContext_BasicOperations(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\terr = e.LoadPolicyCtx(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"LoadPolicyCtx failed: %v\", err)\n\t}\n\n\tadded, err := e.AddPolicyCtx(ctx, \"eve\", \"data3\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPolicyCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"AddPolicyCtx should return true for new policy\")\n\t}\n\n\tremoved, err := e.RemovePolicyCtx(ctx, \"eve\", \"data3\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"RemovePolicyCtx failed: %v\", err)\n\t}\n\tif !removed {\n\t\tt.Error(\"RemovePolicyCtx should return true for existing policy\")\n\t}\n\n\terr = e.SavePolicyCtx(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"SavePolicyCtx failed: %v\", err)\n\t}\n}\n\nfunc TestIEnforcerContext_RBACOperations(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\tadded, err := e.AddRoleForUserCtx(ctx, \"eve\", \"data1_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddRoleForUserCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"AddRoleForUserCtx should return true for new role\")\n\t}\n\n\tadded, err = e.AddPermissionForUserCtx(ctx, \"eve\", \"data3\", \"write\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPermissionForUserCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"AddPermissionForUserCtx should return true for new permission\")\n\t}\n\n\tdeleted, err := e.DeleteRoleForUserCtx(ctx, \"eve\", \"data1_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteRoleForUserCtx failed: %v\", err)\n\t}\n\tif !deleted {\n\t\tt.Error(\"DeleteRoleForUserCtx should return true for existing role\")\n\t}\n\n\tdeleted, err = e.DeletePermissionForUserCtx(ctx, \"eve\", \"data3\", \"write\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeletePermissionForUserCtx failed: %v\", err)\n\t}\n\tif !deleted {\n\t\tt.Error(\"DeletePermissionForUserCtx should return true for existing permission\")\n\t}\n}\n\nfunc TestIEnforcerContext_BatchOperations(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\trules := [][]string{\n\t\t{\"eve\", \"data3\", \"read\"},\n\t\t{\"eve\", \"data3\", \"write\"},\n\t}\n\tadded, err := e.AddPoliciesCtx(ctx, rules)\n\tif err != nil {\n\t\tt.Fatalf(\"AddPoliciesCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"AddPoliciesCtx should return true for new policies\")\n\t}\n\n\tremoved, err := e.RemovePoliciesCtx(ctx, rules)\n\tif err != nil {\n\t\tt.Fatalf(\"RemovePoliciesCtx failed: %v\", err)\n\t}\n\tif !removed {\n\t\tt.Error(\"RemovePoliciesCtx should return true for existing policies\")\n\t}\n}\n\nfunc TestIEnforcerContext_ContextCancellation(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\terr = e.LoadPolicyCtx(ctx)\n\tif err != nil {\n\t\tt.Logf(\"LoadPolicyCtx with cancelled context returned error: %v\", err)\n\t}\n}\n\nfunc TestIEnforcerContext_ContextTimeout(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)\n\tdefer cancel()\n\n\ttime.Sleep(2 * time.Millisecond)\n\n\t_, err = e.AddPolicyCtx(ctx, \"test\", \"data\", \"read\")\n\tif err != nil {\n\t\tt.Logf(\"AddPolicyCtx with timeout context returned error: %v\", err)\n\t}\n}\n\nfunc TestIEnforcerContext_GroupingPolicyOperations(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\tadded, err := e.AddGroupingPolicyCtx(ctx, \"eve\", \"data3_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddGroupingPolicyCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"AddGroupingPolicyCtx should return true for new grouping policy\")\n\t}\n\n\tremoved, err := e.RemoveGroupingPolicyCtx(ctx, \"eve\", \"data3_admin\")\n\tif err != nil {\n\t\tt.Fatalf(\"RemoveGroupingPolicyCtx failed: %v\", err)\n\t}\n\tif !removed {\n\t\tt.Error(\"RemoveGroupingPolicyCtx should return true for existing grouping policy\")\n\t}\n}\n\nfunc TestIEnforcerContext_UpdateOperations(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\t_, err = e.AddPolicyCtx(ctx, \"eve\", \"data3\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPolicyCtx failed: %v\", err)\n\t}\n\n\tupdated, err := e.UpdatePolicyCtx(ctx, []string{\"eve\", \"data3\", \"read\"}, []string{\"eve\", \"data3\", \"write\"})\n\tif err != nil {\n\t\tt.Fatalf(\"UpdatePolicyCtx failed: %v\", err)\n\t}\n\tif !updated {\n\t\tt.Error(\"UpdatePolicyCtx should return true for successful update\")\n\t}\n\n\t_, _ = e.RemovePolicyCtx(ctx, \"eve\", \"data3\", \"write\")\n}\n\nfunc TestIEnforcerContext_SelfMethods(t *testing.T) {\n\te, err := NewContextEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"NewContextEnforcer failed: %v\", err)\n\t}\n\n\tctx := context.Background()\n\n\tadded, err := e.SelfAddPolicyCtx(ctx, \"p\", \"p\", []string{\"eve\", \"data3\", \"read\"})\n\tif err != nil {\n\t\tt.Fatalf(\"SelfAddPolicyCtx failed: %v\", err)\n\t}\n\tif !added {\n\t\tt.Error(\"SelfAddPolicyCtx should return true for new policy\")\n\t}\n\n\tremoved, err := e.SelfRemovePolicyCtx(ctx, \"p\", \"p\", []string{\"eve\", \"data3\", \"read\"})\n\tif err != nil {\n\t\tt.Fatalf(\"SelfRemovePolicyCtx failed: %v\", err)\n\t}\n\tif !removed {\n\t\tt.Error(\"SelfRemovePolicyCtx should return true for existing policy\")\n\t}\n}\n"
  },
  {
    "path": "enforcer_distributed.go",
    "content": "package casbin\n\nimport (\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// DistributedEnforcer wraps SyncedEnforcer for dispatcher.\ntype DistributedEnforcer struct {\n\t*SyncedEnforcer\n}\n\nfunc NewDistributedEnforcer(params ...interface{}) (*DistributedEnforcer, error) {\n\te := &DistributedEnforcer{}\n\tvar err error\n\te.SyncedEnforcer, err = NewSyncedEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn e, nil\n}\n\n// SetDispatcher sets the current dispatcher.\nfunc (d *DistributedEnforcer) SetDispatcher(dispatcher persist.Dispatcher) {\n\td.dispatcher = dispatcher\n}\n\n// AddPoliciesSelf provides a method for dispatcher to add authorization rules to the current policy.\n// The function returns the rules affected and error.\nfunc (d *DistributedEnforcer) AddPoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\tvar noExistsPolicy [][]string\n\t\tfor _, rule := range rules {\n\t\t\tvar hasPolicy bool\n\t\t\thasPolicy, err = d.model.HasPolicy(sec, ptype, rule)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif !hasPolicy {\n\t\t\t\tnoExistsPolicy = append(noExistsPolicy, rule)\n\t\t\t}\n\t\t}\n\n\t\tif err = d.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, noExistsPolicy); err != nil && err.Error() != notImplemented {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\taffected, err = d.model.AddPoliciesWithAffected(sec, ptype, rules)\n\tif err != nil {\n\t\treturn affected, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, affected)\n\t\tif err != nil {\n\t\t\treturn affected, err\n\t\t}\n\t}\n\n\treturn affected, nil\n}\n\n// RemovePoliciesSelf provides a method for dispatcher to remove a set of rules from current policy.\n// The function returns the rules affected and error.\nfunc (d *DistributedEnforcer) RemovePoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\tif err = d.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\taffected, err = d.model.RemovePoliciesWithAffected(sec, ptype, rules)\n\tif err != nil {\n\t\treturn affected, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr = d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)\n\t\tif err != nil {\n\t\t\treturn affected, err\n\t\t}\n\t}\n\n\treturn affected, err\n}\n\n// RemoveFilteredPolicySelf provides a method for dispatcher to remove an authorization rule from the current policy, field filters can be specified.\n// The function returns the rules affected and error.\nfunc (d *DistributedEnforcer) RemoveFilteredPolicySelf(shouldPersist func() bool, sec string, ptype string, fieldIndex int, fieldValues ...string) (affected [][]string, err error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\tif err = d.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, affected, err = d.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\tif err != nil {\n\t\treturn affected, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, affected)\n\t\tif err != nil {\n\t\t\treturn affected, err\n\t\t}\n\t}\n\n\treturn affected, nil\n}\n\n// ClearPolicySelf provides a method for dispatcher to clear all rules from the current policy.\nfunc (d *DistributedEnforcer) ClearPolicySelf(shouldPersist func() bool) error {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\terr := d.adapter.SavePolicy(nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\td.model.ClearPolicy()\n\n\treturn nil\n}\n\n// UpdatePolicySelf provides a method for dispatcher to update an authorization rule from the current policy.\nfunc (d *DistributedEnforcer) UpdatePolicySelf(shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string) (affected bool, err error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\terr = d.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\truleUpdated, err := d.model.UpdatePolicy(sec, ptype, oldRule, newRule)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\n// UpdatePoliciesSelf provides a method for dispatcher to update a set of authorization rules from the current policy.\nfunc (d *DistributedEnforcer) UpdatePoliciesSelf(shouldPersist func() bool, sec string, ptype string, oldRules, newRules [][]string) (affected bool, err error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tif shouldPersist != nil && shouldPersist() {\n\t\terr = d.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\truleUpdated, err := d.model.UpdatePolicies(sec, ptype, oldRules, newRules)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\n// UpdateFilteredPoliciesSelf provides a method for dispatcher to update a set of authorization rules from the current policy.\nfunc (d *DistributedEnforcer) UpdateFilteredPoliciesSelf(shouldPersist func() bool, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\td.m.Lock()\n\tdefer d.m.Unlock()\n\tvar (\n\t\toldRules [][]string\n\t\terr      error\n\t)\n\tif shouldPersist != nil && shouldPersist() {\n\t\toldRules, err = d.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\truleChanged, err := d.model.RemovePolicies(sec, ptype, oldRules)\n\tif err != nil {\n\t\treturn ruleChanged, err\n\t}\n\terr = d.model.AddPolicies(sec, ptype, newRules)\n\tif err != nil {\n\t\treturn ruleChanged, err\n\t}\n\truleChanged = ruleChanged && len(newRules) != 0\n\tif !ruleChanged {\n\t\treturn ruleChanged, nil\n\t}\n\n\tif sec == \"g\" {\n\t\terr := d.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rule\n\t\tif err != nil {\n\t\t\treturn ruleChanged, err\n\t\t}\n\t\terr = d.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rule\n\t\tif err != nil {\n\t\t\treturn ruleChanged, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "enforcer_interface.go",
    "content": "// Copyright 2019 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"github.com/casbin/casbin/v3/effector\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n\t\"github.com/casbin/casbin/v3/rbac\"\n\t\"github.com/casbin/govaluate\"\n)\n\nvar _ IEnforcer = &Enforcer{}\nvar _ IEnforcer = &SyncedEnforcer{}\nvar _ IEnforcer = &CachedEnforcer{}\n\n// IEnforcer is the API interface of Enforcer.\ntype IEnforcer interface {\n\t/* Enforcer API */\n\tInitWithFile(modelPath string, policyPath string) error\n\tInitWithAdapter(modelPath string, adapter persist.Adapter) error\n\tInitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error\n\tLoadModel() error\n\tGetModel() model.Model\n\tSetModel(m model.Model)\n\tGetAdapter() persist.Adapter\n\tSetAdapter(adapter persist.Adapter)\n\tSetWatcher(watcher persist.Watcher) error\n\tGetRoleManager() rbac.RoleManager\n\tSetRoleManager(rm rbac.RoleManager)\n\tSetEffector(eft effector.Effector)\n\tSetAIConfig(config AIConfig)\n\tClearPolicy()\n\tLoadPolicy() error\n\tLoadFilteredPolicy(filter interface{}) error\n\tLoadIncrementalFilteredPolicy(filter interface{}) error\n\tIsFiltered() bool\n\tSavePolicy() error\n\tEnableEnforce(enable bool)\n\tEnableAutoNotifyWatcher(enable bool)\n\tEnableAutoSave(autoSave bool)\n\tEnableAutoBuildRoleLinks(autoBuildRoleLinks bool)\n\tBuildRoleLinks() error\n\tEnforce(rvals ...interface{}) (bool, error)\n\tEnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error)\n\tEnforceEx(rvals ...interface{}) (bool, []string, error)\n\tEnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error)\n\tBatchEnforce(requests [][]interface{}) ([]bool, error)\n\tBatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error)\n\tExplain(rvals ...interface{}) (string, error)\n\n\t/* RBAC API */\n\tGetRolesForUser(name string, domain ...string) ([]string, error)\n\tGetUsersForRole(name string, domain ...string) ([]string, error)\n\tHasRoleForUser(name string, role string, domain ...string) (bool, error)\n\tAddRoleForUser(user string, role string, domain ...string) (bool, error)\n\tAddPermissionForUser(user string, permission ...string) (bool, error)\n\tAddPermissionsForUser(user string, permissions ...[]string) (bool, error)\n\tDeletePermissionForUser(user string, permission ...string) (bool, error)\n\tDeletePermissionsForUser(user string) (bool, error)\n\tGetPermissionsForUser(user string, domain ...string) ([][]string, error)\n\tHasPermissionForUser(user string, permission ...string) (bool, error)\n\tGetImplicitRolesForUser(name string, domain ...string) ([]string, error)\n\tGetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error)\n\tGetImplicitUsersForPermission(permission ...string) ([]string, error)\n\tDeleteRoleForUser(user string, role string, domain ...string) (bool, error)\n\tDeleteRolesForUser(user string, domain ...string) (bool, error)\n\tDeleteUser(user string) (bool, error)\n\tDeleteRole(role string) (bool, error)\n\tDeletePermission(permission ...string) (bool, error)\n\n\t/* RBAC API with domains*/\n\tGetUsersForRoleInDomain(name string, domain string) []string\n\tGetRolesForUserInDomain(name string, domain string) []string\n\tGetPermissionsForUserInDomain(user string, domain string) [][]string\n\tAddRoleForUserInDomain(user string, role string, domain string) (bool, error)\n\tDeleteRoleForUserInDomain(user string, role string, domain string) (bool, error)\n\tGetAllUsersByDomain(domain string) ([]string, error)\n\tDeleteRolesForUserInDomain(user string, domain string) (bool, error)\n\tDeleteAllUsersByDomain(domain string) (bool, error)\n\tDeleteDomains(domains ...string) (bool, error)\n\tGetAllDomains() ([]string, error)\n\tGetAllRolesByDomain(domain string) ([]string, error)\n\n\t/* Management API */\n\tGetAllSubjects() ([]string, error)\n\tGetAllNamedSubjects(ptype string) ([]string, error)\n\tGetAllObjects() ([]string, error)\n\tGetAllNamedObjects(ptype string) ([]string, error)\n\tGetAllActions() ([]string, error)\n\tGetAllNamedActions(ptype string) ([]string, error)\n\tGetAllRoles() ([]string, error)\n\tGetAllNamedRoles(ptype string) ([]string, error)\n\tGetAllUsers() ([]string, error)\n\tGetPolicy() ([][]string, error)\n\tGetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)\n\tGetNamedPolicy(ptype string) ([][]string, error)\n\tGetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)\n\tGetGroupingPolicy() ([][]string, error)\n\tGetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error)\n\tGetNamedGroupingPolicy(ptype string) ([][]string, error)\n\tGetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error)\n\tHasPolicy(params ...interface{}) (bool, error)\n\tHasNamedPolicy(ptype string, params ...interface{}) (bool, error)\n\tAddPolicy(params ...interface{}) (bool, error)\n\tAddPolicies(rules [][]string) (bool, error)\n\tAddNamedPolicy(ptype string, params ...interface{}) (bool, error)\n\tAddNamedPolicies(ptype string, rules [][]string) (bool, error)\n\tAddPoliciesEx(rules [][]string) (bool, error)\n\tAddNamedPoliciesEx(ptype string, rules [][]string) (bool, error)\n\tRemovePolicy(params ...interface{}) (bool, error)\n\tRemovePolicies(rules [][]string) (bool, error)\n\tRemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error)\n\tRemoveNamedPolicy(ptype string, params ...interface{}) (bool, error)\n\tRemoveNamedPolicies(ptype string, rules [][]string) (bool, error)\n\tRemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\tHasGroupingPolicy(params ...interface{}) (bool, error)\n\tHasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)\n\tAddGroupingPolicy(params ...interface{}) (bool, error)\n\tAddGroupingPolicies(rules [][]string) (bool, error)\n\tAddGroupingPoliciesEx(rules [][]string) (bool, error)\n\tAddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)\n\tAddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error)\n\tAddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error)\n\tRemoveGroupingPolicy(params ...interface{}) (bool, error)\n\tRemoveGroupingPolicies(rules [][]string) (bool, error)\n\tRemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error)\n\tRemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)\n\tRemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error)\n\tRemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\tAddFunction(name string, function govaluate.ExpressionFunction)\n\n\tUpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error)\n\tUpdatePolicies(oldPolicies [][]string, newPolicies [][]string) (bool, error)\n\tUpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error)\n\n\tUpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error)\n\tUpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error)\n\tUpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error)\n\tUpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error)\n\n\t/* Management API with autoNotifyWatcher disabled */\n\tSelfAddPolicy(sec string, ptype string, rule []string) (bool, error)\n\tSelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error)\n\tSelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error)\n\tSelfRemovePolicy(sec string, ptype string, rule []string) (bool, error)\n\tSelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error)\n\tSelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error)\n\tSelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error)\n\tSelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error)\n}\n\nvar _ IDistributedEnforcer = &DistributedEnforcer{}\n\n// IDistributedEnforcer defines dispatcher enforcer.\ntype IDistributedEnforcer interface {\n\tIEnforcer\n\tSetDispatcher(dispatcher persist.Dispatcher)\n\t/* Management API for DistributedEnforcer*/\n\tAddPoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error)\n\tRemovePoliciesSelf(shouldPersist func() bool, sec string, ptype string, rules [][]string) (affected [][]string, err error)\n\tRemoveFilteredPolicySelf(shouldPersist func() bool, sec string, ptype string, fieldIndex int, fieldValues ...string) (affected [][]string, err error)\n\tClearPolicySelf(shouldPersist func() bool) error\n\tUpdatePolicySelf(shouldPersist func() bool, sec string, ptype string, oldRule, newRule []string) (affected bool, err error)\n\tUpdatePoliciesSelf(shouldPersist func() bool, sec string, ptype string, oldRules, newRules [][]string) (affected bool, err error)\n\tUpdateFilteredPoliciesSelf(shouldPersist func() bool, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error)\n}\n"
  },
  {
    "path": "enforcer_json_test.go",
    "content": "package casbin\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\nfunc TestInvalidJsonRequest(t *testing.T) {\n\tmodelText := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub.Name == \" \"\n`\n\n\tm, err := model.NewModelFromString(modelText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create model: %v\", err)\n\t}\n\te, err := NewEnforcer(m)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\te.EnableAcceptJsonRequest(true)\n\n\t// Test with invalid JSON (contains \\x escape sequence which is not valid in JSON)\n\tinvalidJSON := `{\"Name\": \"\\x20\"}`\n\t_, err = e.Enforce(invalidJSON, \"obj\", \"read\")\n\tif err == nil {\n\t\tt.Fatalf(\"Expected error for invalid JSON, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"failed to parse JSON parameter\") {\n\t\tt.Fatalf(\"Expected error message to contain 'failed to parse JSON parameter', got: %v\", err)\n\t}\n\n\t// Test with valid JSON - should work\n\tvalidJSON := `{\"Name\": \" \"}`\n\tres, err := e.Enforce(validJSON, \"obj\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Valid JSON should not return error: %v\", err)\n\t}\n\tif !res {\n\t\tt.Fatalf(\"Expected true for valid JSON with matching Name\")\n\t}\n\n\t// Test with plain string (doesn't start with { or [) - should not try to parse as JSON\n\tplainString := \"alice\"\n\t_, err = e.Enforce(plainString, \"obj\", \"read\")\n\t// This will fail because plainString is not a struct with Name field,\n\t// but it shouldn't fail with JSON parsing error\n\tif err != nil && strings.Contains(err.Error(), \"failed to parse JSON parameter\") {\n\t\tt.Fatalf(\"Plain string should not trigger JSON parsing error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "enforcer_synced.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/casbin/govaluate\"\n\n\t\"github.com/casbin/casbin/v3/persist\"\n\t\"github.com/casbin/casbin/v3/rbac\"\n)\n\n// SyncedEnforcer wraps Enforcer and provides synchronized access.\ntype SyncedEnforcer struct {\n\t*Enforcer\n\tm               sync.RWMutex\n\tstopAutoLoad    chan struct{}\n\tautoLoadRunning int32\n}\n\n// NewSyncedEnforcer creates a synchronized enforcer via file or DB.\nfunc NewSyncedEnforcer(params ...interface{}) (*SyncedEnforcer, error) {\n\te := &SyncedEnforcer{}\n\tvar err error\n\te.Enforcer, err = NewEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\te.stopAutoLoad = make(chan struct{}, 1)\n\te.autoLoadRunning = 0\n\treturn e, nil\n}\n\n// GetLock return the private RWMutex lock.\nfunc (e *SyncedEnforcer) GetLock() *sync.RWMutex {\n\treturn &e.m\n}\n\n// GetRoleManager gets the current role manager with synchronization.\nfunc (e *SyncedEnforcer) GetRoleManager() rbac.RoleManager {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetRoleManager()\n}\n\n// GetNamedRoleManager gets the role manager for the named policy with synchronization.\nfunc (e *SyncedEnforcer) GetNamedRoleManager(ptype string) rbac.RoleManager {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetNamedRoleManager(ptype)\n}\n\n// SetRoleManager sets the current role manager with synchronization.\nfunc (e *SyncedEnforcer) SetRoleManager(rm rbac.RoleManager) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\te.Enforcer.SetRoleManager(rm)\n}\n\n// SetNamedRoleManager sets the role manager for the named policy with synchronization.\nfunc (e *SyncedEnforcer) SetNamedRoleManager(ptype string, rm rbac.RoleManager) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\te.Enforcer.SetNamedRoleManager(ptype, rm)\n}\n\n// IsAutoLoadingRunning check if SyncedEnforcer is auto loading policies.\nfunc (e *SyncedEnforcer) IsAutoLoadingRunning() bool {\n\treturn atomic.LoadInt32(&(e.autoLoadRunning)) != 0\n}\n\n// StartAutoLoadPolicy starts a go routine that will every specified duration call LoadPolicy.\nfunc (e *SyncedEnforcer) StartAutoLoadPolicy(d time.Duration) {\n\t// Don't start another goroutine if there is already one running\n\tif !atomic.CompareAndSwapInt32(&e.autoLoadRunning, 0, 1) {\n\t\treturn\n\t}\n\n\tticker := time.NewTicker(d)\n\tgo func() {\n\t\tdefer func() {\n\t\t\tticker.Stop()\n\t\t\tatomic.StoreInt32(&(e.autoLoadRunning), int32(0))\n\t\t}()\n\t\tn := 1\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\t// error intentionally ignored\n\t\t\t\t_ = e.LoadPolicy()\n\t\t\t\t// Uncomment this line to see when the policy is loaded.\n\t\t\t\t// log.Print(\"Load policy for time: \", n)\n\t\t\t\tn++\n\t\t\tcase <-e.stopAutoLoad:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// StopAutoLoadPolicy causes the go routine to exit.\nfunc (e *SyncedEnforcer) StopAutoLoadPolicy() {\n\tif e.IsAutoLoadingRunning() {\n\t\te.stopAutoLoad <- struct{}{}\n\t}\n}\n\n// SetWatcher sets the current watcher.\nfunc (e *SyncedEnforcer) SetWatcher(watcher persist.Watcher) error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SetWatcher(watcher)\n}\n\n// LoadModel reloads the model from the model CONF file.\nfunc (e *SyncedEnforcer) LoadModel() error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.LoadModel()\n}\n\n// ClearPolicy clears all policy.\nfunc (e *SyncedEnforcer) ClearPolicy() {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\te.Enforcer.ClearPolicy()\n}\n\n// LoadPolicy reloads the policy from file/database.\nfunc (e *SyncedEnforcer) LoadPolicy() error {\n\te.m.RLock()\n\tnewModel, err := e.loadPolicyFromAdapter(e.model)\n\te.m.RUnlock()\n\tif err != nil {\n\t\treturn err\n\t}\n\te.m.Lock()\n\terr = e.applyModifiedModel(newModel)\n\te.m.Unlock()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// LoadFilteredPolicy reloads a filtered policy from file/database.\nfunc (e *SyncedEnforcer) LoadFilteredPolicy(filter interface{}) error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.LoadFilteredPolicy(filter)\n}\n\n// LoadIncrementalFilteredPolicy reloads a filtered policy from file/database.\nfunc (e *SyncedEnforcer) LoadIncrementalFilteredPolicy(filter interface{}) error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.LoadIncrementalFilteredPolicy(filter)\n}\n\n// SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database.\nfunc (e *SyncedEnforcer) SavePolicy() error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SavePolicy()\n}\n\n// BuildRoleLinks manually rebuild the role inheritance relations.\nfunc (e *SyncedEnforcer) BuildRoleLinks() error {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.BuildRoleLinks()\n}\n\n// Enforce decides whether a \"subject\" can access a \"object\" with the operation \"action\", input parameters are usually: (sub, obj, act).\nfunc (e *SyncedEnforcer) Enforce(rvals ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.Enforce(rvals...)\n}\n\n// 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 \"\".\nfunc (e *SyncedEnforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.EnforceWithMatcher(matcher, rvals...)\n}\n\n// EnforceEx explain enforcement by informing matched rules.\nfunc (e *SyncedEnforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.EnforceEx(rvals...)\n}\n\n// EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules.\nfunc (e *SyncedEnforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.EnforceExWithMatcher(matcher, rvals...)\n}\n\n// BatchEnforce enforce in batches.\nfunc (e *SyncedEnforcer) BatchEnforce(requests [][]interface{}) ([]bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.BatchEnforce(requests)\n}\n\n// BatchEnforceWithMatcher enforce with matcher in batches.\nfunc (e *SyncedEnforcer) BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.BatchEnforceWithMatcher(matcher, requests)\n}\n\n// GetAllSubjects gets the list of subjects that show up in the current policy.\nfunc (e *SyncedEnforcer) GetAllSubjects() ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllSubjects()\n}\n\n// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.\nfunc (e *SyncedEnforcer) GetAllNamedSubjects(ptype string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllNamedSubjects(ptype)\n}\n\n// GetAllObjects gets the list of objects that show up in the current policy.\nfunc (e *SyncedEnforcer) GetAllObjects() ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllObjects()\n}\n\n// GetAllNamedObjects gets the list of objects that show up in the current named policy.\nfunc (e *SyncedEnforcer) GetAllNamedObjects(ptype string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllNamedObjects(ptype)\n}\n\n// GetAllActions gets the list of actions that show up in the current policy.\nfunc (e *SyncedEnforcer) GetAllActions() ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllActions()\n}\n\n// GetAllNamedActions gets the list of actions that show up in the current named policy.\nfunc (e *SyncedEnforcer) GetAllNamedActions(ptype string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllNamedActions(ptype)\n}\n\n// GetAllRoles gets the list of roles that show up in the current policy.\nfunc (e *SyncedEnforcer) GetAllRoles() ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllRoles()\n}\n\n// GetAllNamedRoles gets the list of roles that show up in the current named policy.\nfunc (e *SyncedEnforcer) GetAllNamedRoles(ptype string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllNamedRoles(ptype)\n}\n\n// GetAllUsers gets the list of users that show up in the current policy.\nfunc (e *SyncedEnforcer) GetAllUsers() ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetAllUsers()\n}\n\n// GetPolicy gets all the authorization rules in the policy.\nfunc (e *SyncedEnforcer) GetPolicy() ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetPolicy()\n}\n\n// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.\nfunc (e *SyncedEnforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetFilteredPolicy(fieldIndex, fieldValues...)\n}\n\n// GetNamedPolicy gets all the authorization rules in the named policy.\nfunc (e *SyncedEnforcer) GetNamedPolicy(ptype string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetNamedPolicy(ptype)\n}\n\n// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.\nfunc (e *SyncedEnforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetFilteredNamedPolicy(ptype, fieldIndex, fieldValues...)\n}\n\n// GetGroupingPolicy gets all the role inheritance rules in the policy.\nfunc (e *SyncedEnforcer) GetGroupingPolicy() ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetGroupingPolicy()\n}\n\n// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.\nfunc (e *SyncedEnforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetFilteredGroupingPolicy(fieldIndex, fieldValues...)\n}\n\n// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.\nfunc (e *SyncedEnforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetNamedGroupingPolicy(ptype)\n}\n\n// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.\nfunc (e *SyncedEnforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...)\n}\n\n// HasPolicy determines whether an authorization rule exists.\nfunc (e *SyncedEnforcer) HasPolicy(params ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasPolicy(params...)\n}\n\n// HasNamedPolicy determines whether a named authorization rule exists.\nfunc (e *SyncedEnforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasNamedPolicy(ptype, params...)\n}\n\n// AddPolicy adds an authorization rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *SyncedEnforcer) AddPolicy(params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddPolicy(params...)\n}\n\n// AddPolicies adds authorization rules to the current policy.\n// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding rule by adding the new rule.\nfunc (e *SyncedEnforcer) AddPolicies(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddPolicies(rules)\n}\n\n// AddPoliciesEx adds authorization rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *SyncedEnforcer) AddPoliciesEx(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddPoliciesEx(rules)\n}\n\n// AddNamedPolicy adds an authorization rule to the current named policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *SyncedEnforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedPolicy(ptype, params...)\n}\n\n// AddNamedPolicies adds authorization rules to the current named policy.\n// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding by adding the new rule.\nfunc (e *SyncedEnforcer) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedPolicies(ptype, rules)\n}\n\n// AddNamedPoliciesEx adds authorization rules to the current named policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *SyncedEnforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedPoliciesEx(ptype, rules)\n}\n\n// RemovePolicy removes an authorization rule from the current policy.\nfunc (e *SyncedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemovePolicy(params...)\n}\n\n// UpdatePolicy updates an authorization rule from the current policy.\nfunc (e *SyncedEnforcer) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdatePolicy(oldPolicy, newPolicy)\n}\n\nfunc (e *SyncedEnforcer) UpdateNamedPolicy(ptype string, p1 []string, p2 []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateNamedPolicy(ptype, p1, p2)\n}\n\n// UpdatePolicies updates authorization rules from the current policies.\nfunc (e *SyncedEnforcer) UpdatePolicies(oldPolices [][]string, newPolicies [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdatePolicies(oldPolices, newPolicies)\n}\n\nfunc (e *SyncedEnforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateNamedPolicies(ptype, p1, p2)\n}\n\nfunc (e *SyncedEnforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateFilteredPolicies(newPolicies, fieldIndex, fieldValues...)\n}\n\nfunc (e *SyncedEnforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateFilteredNamedPolicies(ptype, newPolicies, fieldIndex, fieldValues...)\n}\n\n// RemovePolicies removes authorization rules from the current policy.\nfunc (e *SyncedEnforcer) RemovePolicies(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemovePolicies(rules)\n}\n\n// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified.\nfunc (e *SyncedEnforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveFilteredPolicy(fieldIndex, fieldValues...)\n}\n\n// RemoveNamedPolicy removes an authorization rule from the current named policy.\nfunc (e *SyncedEnforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveNamedPolicy(ptype, params...)\n}\n\n// RemoveNamedPolicies removes authorization rules from the current named policy.\nfunc (e *SyncedEnforcer) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveNamedPolicies(ptype, rules)\n}\n\n// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified.\nfunc (e *SyncedEnforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveFilteredNamedPolicy(ptype, fieldIndex, fieldValues...)\n}\n\n// HasGroupingPolicy determines whether a role inheritance rule exists.\nfunc (e *SyncedEnforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasGroupingPolicy(params...)\n}\n\n// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.\nfunc (e *SyncedEnforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasNamedGroupingPolicy(ptype, params...)\n}\n\n// AddGroupingPolicy adds a role inheritance rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *SyncedEnforcer) AddGroupingPolicy(params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddGroupingPolicy(params...)\n}\n\n// AddGroupingPolicies adds role inheritance rulea to the current policy.\n// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding policy rule by adding the new rule.\nfunc (e *SyncedEnforcer) AddGroupingPolicies(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddGroupingPolicies(rules)\n}\n\n// AddGroupingPoliciesEx adds role inheritance rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *SyncedEnforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddGroupingPoliciesEx(rules)\n}\n\n// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *SyncedEnforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedGroupingPolicy(ptype, params...)\n}\n\n// AddNamedGroupingPolicies adds named role inheritance rules to the current policy.\n// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding policy rule by adding the new rule.\nfunc (e *SyncedEnforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedGroupingPolicies(ptype, rules)\n}\n\n// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *SyncedEnforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddNamedGroupingPoliciesEx(ptype, rules)\n}\n\n// RemoveGroupingPolicy removes a role inheritance rule from the current policy.\nfunc (e *SyncedEnforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveGroupingPolicy(params...)\n}\n\n// RemoveGroupingPolicies removes role inheritance rules from the current policy.\nfunc (e *SyncedEnforcer) RemoveGroupingPolicies(rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveGroupingPolicies(rules)\n}\n\n// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified.\nfunc (e *SyncedEnforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveFilteredGroupingPolicy(fieldIndex, fieldValues...)\n}\n\n// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy.\nfunc (e *SyncedEnforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveNamedGroupingPolicy(ptype, params...)\n}\n\n// RemoveNamedGroupingPolicies removes role inheritance rules from the current named policy.\nfunc (e *SyncedEnforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveNamedGroupingPolicies(ptype, rules)\n}\n\nfunc (e *SyncedEnforcer) UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateGroupingPolicy(oldRule, newRule)\n}\n\nfunc (e *SyncedEnforcer) UpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateGroupingPolicies(oldRules, newRules)\n}\n\nfunc (e *SyncedEnforcer) UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateNamedGroupingPolicy(ptype, oldRule, newRule)\n}\n\nfunc (e *SyncedEnforcer) UpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.UpdateNamedGroupingPolicies(ptype, oldRules, newRules)\n}\n\n// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified.\nfunc (e *SyncedEnforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.RemoveFilteredNamedGroupingPolicy(ptype, fieldIndex, fieldValues...)\n}\n\n// AddFunction adds a customized function.\nfunc (e *SyncedEnforcer) AddFunction(name string, function govaluate.ExpressionFunction) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\te.Enforcer.AddFunction(name, function)\n}\n\nfunc (e *SyncedEnforcer) SelfAddPolicy(sec string, ptype string, rule []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfAddPolicy(sec, ptype, rule)\n}\n\nfunc (e *SyncedEnforcer) SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfAddPolicies(sec, ptype, rules)\n}\n\nfunc (e *SyncedEnforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfAddPoliciesEx(sec, ptype, rules)\n}\n\nfunc (e *SyncedEnforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfRemovePolicy(sec, ptype, rule)\n}\n\nfunc (e *SyncedEnforcer) SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfRemovePolicies(sec, ptype, rules)\n}\n\nfunc (e *SyncedEnforcer) SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n}\n\nfunc (e *SyncedEnforcer) SelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfUpdatePolicy(sec, ptype, oldRule, newRule)\n}\n\nfunc (e *SyncedEnforcer) SelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.SelfUpdatePolicies(sec, ptype, oldRules, newRules)\n}\n"
  },
  {
    "path": "enforcer_synced_test.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc testEnforceSync(t *testing.T, e *SyncedEnforcer, sub string, obj interface{}, act string, res bool) {\n\tt.Helper()\n\tif myRes, _ := e.Enforce(sub, obj, act); myRes != res {\n\t\tt.Errorf(\"%s, %v, %s: %t, supposed to be %t\", sub, obj, act, myRes, res)\n\t}\n}\n\nfunc TestSync(t *testing.T) {\n\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t// Start reloading the policy every 200 ms.\n\te.StartAutoLoadPolicy(time.Millisecond * 200)\n\n\ttestEnforceSync(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforceSync(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforceSync(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforceSync(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforceSync(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforceSync(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforceSync(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforceSync(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t// Simulate a policy change\n\te.ClearPolicy()\n\ttestEnforceSync(t, e, \"bob\", \"data2\", \"write\", false)\n\n\t// Wait for at least one sync\n\ttime.Sleep(time.Millisecond * 300)\n\n\ttestEnforceSync(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t// Stop the reloading policy periodically.\n\te.StopAutoLoadPolicy()\n}\n\nfunc TestStopAutoLoadPolicy(t *testing.T) {\n\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\te.StartAutoLoadPolicy(5 * time.Millisecond)\n\tif !e.IsAutoLoadingRunning() {\n\t\tt.Error(\"auto load is not running\")\n\t}\n\te.StopAutoLoadPolicy()\n\t// Need a moment, to exit goroutine\n\ttime.Sleep(10 * time.Millisecond)\n\tif e.IsAutoLoadingRunning() {\n\t\tt.Error(\"auto load is still running\")\n\t}\n}\n\nfunc testSyncedEnforcerGetPolicy(t *testing.T, e *SyncedEnforcer, res [][]string) {\n\tt.Helper()\n\tmyRes, err := e.GetPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif !util.SortedArray2DEquals(res, myRes) {\n\t\tt.Error(\"Policy: \", myRes, \", supposed to be \", res)\n\t} else {\n\t\tt.Log(\"Policy: \", myRes)\n\t}\n}\n\nfunc TestSyncedEnforcerSelfAddPolicy(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfAddPolicies(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPolicies(\"p\", \"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPolicies(\"p\", \"p\", [][]string{{\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPolicies(\"p\", \"p\", [][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}})\n\t\t}()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfAddPoliciesEx(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user4\", \"data4\", \"read\"}, {\"user5\", \"data5\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user6\", \"data6\", \"read\"}, {\"user1\", \"data1\", \"read\"}})\n\t\t}()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfRemovePolicy(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfRemovePolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfRemovePolicies(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\n\t\tgo func() {\n\t\t\t_, _ = e.SelfRemovePolicies(\"p\", \"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfRemovePolicies(\"p\", \"p\", [][]string{{\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfRemovePolicies(\"p\", \"p\", [][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}})\n\t\t}()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfRemoveFilteredPolicy(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user7\", \"data7\", \"write\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user8\", \"data8\", \"write\"}) }()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t\t{\"user7\", \"data7\", \"write\"},\n\t\t\t{\"user8\", \"data8\", \"write\"},\n\t\t})\n\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 0, \"user1\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 0, \"user2\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 1, \"data3\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 1, \"data4\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 0, \"user5\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 0, \"user6\") }()\n\t\tgo func() { _, _ = e.SelfRemoveFilteredPolicy(\"p\", \"p\", 2, \"write\") }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfUpdatePolicy(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}, []string{\"user1\", \"data1\", \"write\"})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}, []string{\"user2\", \"data2\", \"write\"})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}, []string{\"user3\", \"data3\", \"write\"})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}, []string{\"user4\", \"data4\", \"write\"})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}, []string{\"user5\", \"data5\", \"write\"})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}, []string{\"user6\", \"data6\", \"write\"})\n\t\t}()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"write\"},\n\t\t\t{\"user2\", \"data2\", \"write\"},\n\t\t\t{\"user3\", \"data3\", \"write\"},\n\t\t\t{\"user4\", \"data4\", \"write\"},\n\t\t\t{\"user5\", \"data5\", \"write\"},\n\t\t\t{\"user6\", \"data6\", \"write\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerSelfUpdatePolicies(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user1\", \"data1\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user2\", \"data2\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user3\", \"data3\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user4\", \"data4\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user5\", \"data5\", \"read\"}) }()\n\t\tgo func() { _, _ = e.SelfAddPolicy(\"p\", \"p\", []string{\"user6\", \"data6\", \"read\"}) }()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicies(\"p\", \"p\",\n\t\t\t\t[][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}},\n\t\t\t\t[][]string{{\"user1\", \"data1\", \"write\"}, {\"user2\", \"data2\", \"write\"}})\n\t\t}()\n\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicies(\"p\", \"p\",\n\t\t\t\t[][]string{{\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}},\n\t\t\t\t[][]string{{\"user3\", \"data3\", \"write\"}, {\"user4\", \"data4\", \"write\"}})\n\t\t}()\n\n\t\tgo func() {\n\t\t\t_, _ = e.SelfUpdatePolicies(\"p\", \"p\",\n\t\t\t\t[][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}},\n\t\t\t\t[][]string{{\"user5\", \"data5\", \"write\"}, {\"user6\", \"data6\", \"write\"}})\n\t\t}()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"write\"},\n\t\t\t{\"user2\", \"data2\", \"write\"},\n\t\t\t{\"user3\", \"data3\", \"write\"},\n\t\t\t{\"user4\", \"data4\", \"write\"},\n\t\t\t{\"user5\", \"data5\", \"write\"},\n\t\t\t{\"user6\", \"data6\", \"write\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerAddPoliciesEx(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user4\", \"data4\", \"read\"}, {\"user5\", \"data5\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user4\", \"data4\", \"read\"}, {\"user5\", \"data5\", \"read\"}}) }()\n\t\tgo func() { _, _ = e.AddPoliciesEx([][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}}) }()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc TestSyncedEnforcerAddNamedPoliciesEx(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user4\", \"data4\", \"read\"}, {\"user5\", \"data5\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user4\", \"data4\", \"read\"}, {\"user5\", \"data5\", \"read\"}})\n\t\t}()\n\t\tgo func() {\n\t\t\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user5\", \"data5\", \"read\"}, {\"user6\", \"data6\", \"read\"}})\n\t\t}()\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetPolicy(t, e, [][]string{\n\t\t\t{\"alice\", \"data1\", \"read\"},\n\t\t\t{\"bob\", \"data2\", \"write\"},\n\t\t\t{\"user1\", \"data1\", \"read\"},\n\t\t\t{\"user2\", \"data2\", \"read\"},\n\t\t\t{\"user3\", \"data3\", \"read\"},\n\t\t\t{\"user4\", \"data4\", \"read\"},\n\t\t\t{\"user5\", \"data5\", \"read\"},\n\t\t\t{\"user6\", \"data6\", \"read\"},\n\t\t})\n\t}\n}\n\nfunc testSyncedEnforcerGetUsers(t *testing.T, e *SyncedEnforcer, res []string, name string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetUsersForRole(name, domain...)\n\tmyResCopy := make([]string, len(myRes))\n\tcopy(myResCopy, myRes)\n\tsort.Strings(myRes)\n\tsort.Strings(res)\n\tswitch err {\n\tcase nil:\n\t\tbreak\n\tcase errors.ErrNameNotFound:\n\t\tt.Log(\"No name found\")\n\tdefault:\n\t\tt.Error(\"Users for \", name, \" could not be fetched: \", err.Error())\n\t}\n\tt.Log(\"Users for \", name, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Users for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\nfunc TestSyncedEnforcerAddGroupingPoliciesEx(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\t\te.ClearPolicy()\n\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user2\", \"member\"}, {\"user3\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user4\", \"member\"}, {\"user5\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user5\", \"member\"}, {\"user6\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user2\", \"member\"}, {\"user3\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user4\", \"member\"}, {\"user5\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddGroupingPoliciesEx([][]string{{\"user5\", \"member\"}, {\"user6\", \"member\"}}) }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetUsers(t, e, []string{\"user1\", \"user2\", \"user3\", \"user4\", \"user5\", \"user6\"}, \"member\")\n\t}\n}\n\nfunc TestSyncedEnforcerAddNamedGroupingPoliciesEx(t *testing.T) {\n\tfor i := 0; i < 10; i++ {\n\t\te, _ := NewSyncedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\t\te.ClearPolicy()\n\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user2\", \"member\"}, {\"user3\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user4\", \"member\"}, {\"user5\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user5\", \"member\"}, {\"user6\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user2\", \"member\"}, {\"user3\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user4\", \"member\"}, {\"user5\", \"member\"}}) }()\n\t\tgo func() { _, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user5\", \"member\"}, {\"user6\", \"member\"}}) }()\n\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\ttestSyncedEnforcerGetUsers(t, e, []string{\"user1\", \"user2\", \"user3\", \"user4\", \"user5\", \"user6\"}, \"member\")\n\t}\n}\n"
  },
  {
    "path": "enforcer_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/detector\"\n\t\"github.com/casbin/casbin/v3/model\"\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc TestKeyMatchModelInMemory(t *testing.T) {\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\tm.AddDef(\"m\", \"m\", \"r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)\")\n\n\ta := fileadapter.NewAdapter(\"examples/keymatch_policy.csv\")\n\n\te, _ := NewEnforcer(m, a)\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"POST\", false)\n\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"POST\", true)\n\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"GET\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"POST\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"DELETE\", false)\n\n\te, _ = NewEnforcer(m)\n\t_ = a.LoadPolicy(e.GetModel())\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"POST\", false)\n\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"POST\", true)\n\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"GET\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"POST\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"DELETE\", false)\n}\n\nfunc TestKeyMatchWithRBACInDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/keymatch_with_rbac_in_domain.conf\", \"examples/keymatch_with_rbac_in_domain.csv\")\n\ttestDomainEnforce(t, e, \"Username==test2\", \"engines/engine1\", \"*\", \"attach\", true)\n}\n\nfunc TestKeyMatchModelInMemoryDeny(t *testing.T) {\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"e\", \"e\", \"!some(where (p.eft == deny))\")\n\tm.AddDef(\"m\", \"m\", \"r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)\")\n\n\ta := fileadapter.NewAdapter(\"examples/keymatch_policy.csv\")\n\n\te, _ := NewEnforcer(m, a)\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"POST\", true)\n}\n\nfunc TestRBACModelInMemoryIndeterminate(t *testing.T) {\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"g\", \"g\", \"_, _\")\n\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\tm.AddDef(\"m\", \"m\", \"g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\")\n\n\te, _ := NewEnforcer(m)\n\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"invalid\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n}\n\nfunc TestRBACModelInMemory(t *testing.T) {\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"g\", \"g\", \"_, _\")\n\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\tm.AddDef(\"m\", \"m\", \"g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\")\n\n\te, _ := NewEnforcer(m)\n\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"read\")\n\t_, _ = e.AddPermissionForUser(\"bob\", \"data2\", \"write\")\n\t_, _ = e.AddPermissionForUser(\"data2_admin\", \"data2\", \"read\")\n\t_, _ = e.AddPermissionForUser(\"data2_admin\", \"data2\", \"write\")\n\t_, _ = e.AddRoleForUser(\"alice\", \"data2_admin\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelInMemory2(t *testing.T) {\n\ttext :=\n\t\t`\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\tm, _ := model.NewModelFromString(text)\n\t// The above is the same as:\n\t// m := NewModel()\n\t// m.LoadModelFromText(text)\n\n\te, _ := NewEnforcer(m)\n\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"read\")\n\t_, _ = e.AddPermissionForUser(\"bob\", \"data2\", \"write\")\n\t_, _ = e.AddPermissionForUser(\"data2_admin\", \"data2\", \"read\")\n\t_, _ = e.AddPermissionForUser(\"data2_admin\", \"data2\", \"write\")\n\t_, _ = e.AddRoleForUser(\"alice\", \"data2_admin\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestNotUsedRBACModelInMemory(t *testing.T) {\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"g\", \"g\", \"_, _\")\n\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\tm.AddDef(\"m\", \"m\", \"g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\")\n\n\te, _ := NewEnforcer(m)\n\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"read\")\n\t_, _ = e.AddPermissionForUser(\"bob\", \"data2\", \"write\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestMatcherUsingInOperator(t *testing.T) {\n\t// From file config\n\te, _ := NewEnforcer(\"examples/rbac_model_matcher_using_in_op.conf\")\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"read\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"read\", true)\n\ttestEnforce(t, e, \"anyone\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"anyone\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"anyone\", \"data3\", \"read\", true)\n}\n\nfunc TestMatcherUsingInOperatorBracket(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model_matcher_using_in_op_bracket.conf\")\n\t_, _ = e.AddPermissionForUser(\"alice\", \"data1\", \"read\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"read\", true)\n\ttestEnforce(t, e, \"anyone\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"anyone\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"anyone\", \"data3\", \"read\", true)\n}\n\nfunc TestReloadPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t_ = e.LoadPolicy()\n\ttestGetPolicy(t, e, [][]string{{\"alice\", \"data1\", \"read\"}, {\"bob\", \"data2\", \"write\"}, {\"data2_admin\", \"data2\", \"read\"}, {\"data2_admin\", \"data2\", \"write\"}})\n}\n\nfunc TestSavePolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t_ = e.SavePolicy()\n}\n\nfunc TestClearPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\te.ClearPolicy()\n}\n\nfunc TestEnableEnforce(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\te.EnableEnforce(false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\te.EnableEnforce(true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestEnableLog(t *testing.T) {\n\t// This test is now a no-op since the logger has been removed\n\t// Keeping it for backward compatibility, but it just tests enforcement\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestEnableAutoSave(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\te.EnableAutoSave(false)\n\t// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,\n\t// it doesn't affect the policy in the storage.\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\t// Reload the policy from the storage to see the effect.\n\t_ = e.LoadPolicy()\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\te.EnableAutoSave(true)\n\t// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,\n\t// but also affects the policy in the storage.\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\n\t// However, the file adapter doesn't implement the AutoSave feature, so enabling it has no effect at all here.\n\n\t// Reload the policy from the storage to see the effect.\n\t_ = e.LoadPolicy()\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true) // Will not be false here.\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestInitWithAdapter(t *testing.T) {\n\tadapter := fileadapter.NewAdapter(\"examples/basic_policy.csv\")\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", adapter)\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRoleLinks(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\te.EnableAutoBuildRoleLinks(false)\n\t_ = e.BuildRoleLinks()\n\t_, _ = e.Enforce(\"user501\", \"data9\", \"read\")\n}\n\nfunc TestEnforceConcurrency(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Errorf(\"Enforce is not concurrent\")\n\t\t}\n\t}()\n\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\t_ = e.LoadModel()\n\n\tvar wg sync.WaitGroup\n\n\t// Simulate concurrency (maybe use a timer?)\n\tfor i := 1; i <= 10000; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\t_, _ = e.Enforce(\"user501\", \"data9\", \"read\")\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\twg.Wait()\n}\n\nfunc TestGetAndSetModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\te2, _ := NewEnforcer(\"examples/basic_with_root_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforce(t, e, \"root\", \"data1\", \"read\", false)\n\n\te.SetModel(e2.GetModel())\n\n\ttestEnforce(t, e, \"root\", \"data1\", \"read\", true)\n}\n\nfunc TestGetAndSetAdapterInMem(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\te2, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_inverse_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\n\ta2 := e2.GetAdapter()\n\te.SetAdapter(a2)\n\t_ = e.LoadPolicy()\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n}\n\nfunc TestSetAdapterFromFile(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\n\ta := fileadapter.NewAdapter(\"examples/basic_policy.csv\")\n\te.SetAdapter(a)\n\t_ = e.LoadPolicy()\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n}\n\nfunc TestInitEmpty(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tm := model.NewModel()\n\tm.AddDef(\"r\", \"r\", \"sub, obj, act\")\n\tm.AddDef(\"p\", \"p\", \"sub, obj, act\")\n\tm.AddDef(\"e\", \"e\", \"some(where (p.eft == allow))\")\n\tm.AddDef(\"m\", \"m\", \"r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)\")\n\n\ta := fileadapter.NewAdapter(\"examples/keymatch_policy.csv\")\n\n\te.SetModel(m)\n\te.SetAdapter(a)\n\t_ = e.LoadPolicy()\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"GET\", true)\n}\nfunc testEnforceEx(t *testing.T, e *Enforcer, sub, obj, act interface{}, res []string) {\n\tt.Helper()\n\t_, myRes, _ := e.EnforceEx(sub, obj, act)\n\n\tif ok := util.ArrayEquals(res, myRes); !ok {\n\t\tt.Error(\"Key: \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestEnforceEx(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"read\", []string{\"alice\", \"data1\", \"read\"})\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"write\", []string{\"bob\", \"data2\", \"write\"})\n\n\te, _ = NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"read\", []string{\"alice\", \"data1\", \"read\"})\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"read\", []string{\"data2_admin\", \"data2\", \"read\"})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"write\", []string{\"data2_admin\", \"data2\", \"write\"})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"write\", []string{\"bob\", \"data2\", \"write\"})\n\n\te, _ = NewEnforcer(\"examples/priority_model.conf\", \"examples/priority_policy.csv\")\n\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"read\", []string{\"alice\", \"data1\", \"read\", \"allow\"})\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"write\", []string{\"data1_deny_group\", \"data1\", \"write\", \"deny\"})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"read\", []string{\"data2_allow_group\", \"data2\", \"read\", \"allow\"})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"write\", []string{\"bob\", \"data2\", \"write\", \"deny\"})\n\n\te, _ = NewEnforcer(\"examples/abac_model.conf\")\n\tobj := struct{ Owner string }{Owner: \"alice\"}\n\ttestEnforceEx(t, e, \"alice\", obj, \"write\", []string{})\n}\n\nfunc TestEnforceExLog(t *testing.T) {\n\t// This test was previously named for logging, but actually tests EnforceEx explain functionality\n\t// Logger parameter has been removed, but the test still validates explain behavior\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"read\", []string{\"alice\", \"data1\", \"read\"})\n\ttestEnforceEx(t, e, \"alice\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"alice\", \"data2\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data1\", \"write\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"read\", []string{})\n\ttestEnforceEx(t, e, \"bob\", \"data2\", \"write\", []string{\"bob\", \"data2\", \"write\"})\n}\n\nfunc testBatchEnforce(t *testing.T, e *Enforcer, requests [][]interface{}, results []bool) {\n\tt.Helper()\n\tmyRes, _ := e.BatchEnforce(requests)\n\tif len(myRes) != len(results) {\n\t\tt.Errorf(\"%v supposed to be %v\", myRes, results)\n\t}\n\tfor i, v := range myRes {\n\t\tif v != results[i] {\n\t\t\tt.Errorf(\"%v supposed to be %v\", myRes, results)\n\t\t}\n\t}\n}\n\nfunc TestBatchEnforce(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\tresults := []bool{true, true, false}\n\ttestBatchEnforce(t, e, [][]interface{}{{\"alice\", \"data1\", \"read\"}, {\"bob\", \"data2\", \"write\"}, {\"jack\", \"data3\", \"read\"}}, results)\n}\n\nfunc TestSubjectPriority(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/subject_priority_model.conf\", \"examples/subject_priority_policy.csv\")\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"jane\", \"data1\", \"read\"},\n\t\t{\"alice\", \"data1\", \"read\"},\n\t}, []bool{\n\t\ttrue, true,\n\t})\n}\n\nfunc TestSubjectPriorityWithDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/subject_priority_model_with_domain.conf\", \"examples/subject_priority_policy_with_domain.csv\")\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"alice\", \"data1\", \"domain1\", \"write\"},\n\t\t{\"bob\", \"data2\", \"domain2\", \"write\"},\n\t}, []bool{\n\t\ttrue, true,\n\t})\n}\n\nfunc TestSubjectPriorityInFilter(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/subject_priority_policy_with_domain.csv\")\n\t_ = e.InitWithAdapter(\"examples/subject_priority_model_with_domain.conf\", adapter)\n\tif err := e.loadFilteredPolicy(&fileadapter.Filter{\n\t\tP: []string{\"\", \"\", \"domain1\"},\n\t}); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"alice\", \"data1\", \"domain1\", \"write\"},\n\t\t{\"admin\", \"data1\", \"domain1\", \"write\"},\n\t}, []bool{\n\t\ttrue, false,\n\t})\n}\n\nfunc TestMultiplePolicyDefinitions(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/multiple_policy_definitions_model.conf\", \"examples/multiple_policy_definitions_policy.csv\")\n\tenforceContext := NewEnforceContext(\"2\")\n\tenforceContext.EType = \"e\"\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"alice\", \"data2\", \"read\"},\n\t\t{enforceContext, struct{ Age int }{Age: 70}, \"/data1\", \"read\"},\n\t\t{enforceContext, struct{ Age int }{Age: 30}, \"/data1\", \"read\"},\n\t}, []bool{\n\t\ttrue, false, true,\n\t})\n}\n\nfunc TestPriorityExplicit(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/priority_model_explicit.conf\", \"examples/priority_policy_explicit.csv\")\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"alice\", \"data1\", \"write\"},\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t\t{\"data1_deny_group\", \"data1\", \"read\"},\n\t\t{\"data1_deny_group\", \"data1\", \"write\"},\n\t\t{\"data2_allow_group\", \"data2\", \"read\"},\n\t\t{\"data2_allow_group\", \"data2\", \"write\"},\n\t}, []bool{\n\t\ttrue, true, false, true, false, false, true, true,\n\t})\n\n\t_, err := e.AddPolicy(\"1\", \"bob\", \"data2\", \"write\", \"deny\")\n\tif err != nil {\n\t\tt.Fatalf(\"Add Policy: %v\", err)\n\t}\n\n\ttestBatchEnforce(t, e, [][]interface{}{\n\t\t{\"alice\", \"data1\", \"write\"},\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t\t{\"data1_deny_group\", \"data1\", \"read\"},\n\t\t{\"data1_deny_group\", \"data1\", \"write\"},\n\t\t{\"data2_allow_group\", \"data2\", \"read\"},\n\t\t{\"data2_allow_group\", \"data2\", \"write\"},\n\t}, []bool{\n\t\ttrue, true, false, false, false, false, true, true,\n\t})\n}\n\nfunc TestFailedToLoadPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\te.AddNamedMatchingFunc(\"g2\", \"matchingFunc\", util.KeyMatch2)\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen/3\", \"GET\", true)\n\te.SetAdapter(fileadapter.NewAdapter(\"not found\"))\n\t_ = e.LoadPolicy()\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen/3\", \"GET\", true)\n}\n\nfunc TestReloadPolicyWithFunc(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\te.AddNamedMatchingFunc(\"g2\", \"matchingFunc\", util.KeyMatch2)\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen/3\", \"GET\", true)\n\t_ = e.LoadPolicy()\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen/3\", \"GET\", true)\n}\n\nfunc TestEvalPriority(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/eval_operator_model.conf\", \"examples/eval_operator_policy.csv\")\n\ttestEnforce(t, e, \"admin\", \"users\", \"write\", true)\n\ttestEnforce(t, e, \"admin\", \"none\", \"write\", false)\n\ttestEnforce(t, e, \"user\", \"users\", \"write\", false)\n}\n\nfunc TestLinkConditionFunc(t *testing.T) {\n\tTrueFunc := func(args ...string) (bool, error) {\n\t\tif len(args) != 0 {\n\t\t\treturn args[0] == \"_\" || args[0] == \"true\", nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tFalseFunc := func(args ...string) (bool, error) {\n\t\tif len(args) != 0 {\n\t\t\treturn args[0] == \"_\" || args[0] == \"false\", nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\tm, _ := model.NewModelFromFile(\"examples/rbac_with_temporal_roles_model.conf\")\n\te, _ := NewEnforcer(m)\n\n\t_, _ = e.AddPolicies([][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"alice\", \"data1\", \"write\"},\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"},\n\t\t{\"data3_admin\", \"data3\", \"read\"},\n\t\t{\"data3_admin\", \"data3\", \"write\"},\n\t\t{\"data4_admin\", \"data4\", \"read\"},\n\t\t{\"data4_admin\", \"data4\", \"write\"},\n\t\t{\"data5_admin\", \"data5\", \"read\"},\n\t\t{\"data5_admin\", \"data5\", \"write\"},\n\t})\n\n\t_, _ = e.AddGroupingPolicies([][]string{\n\t\t{\"alice\", \"data2_admin\", \"_\", \"_\"},\n\t\t{\"alice\", \"data3_admin\", \"_\", \"_\"},\n\t\t{\"alice\", \"data4_admin\", \"_\", \"_\"},\n\t\t{\"alice\", \"data5_admin\", \"_\", \"_\"},\n\t})\n\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data2_admin\", TrueFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data3_admin\", TrueFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data4_admin\", FalseFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data5_admin\", FalseFunc)\n\n\te.SetNamedLinkConditionFuncParams(\"g\", \"alice\", \"data2_admin\", \"true\")\n\te.SetNamedLinkConditionFuncParams(\"g\", \"alice\", \"data3_admin\", \"not true\")\n\te.SetNamedLinkConditionFuncParams(\"g\", \"alice\", \"data4_admin\", \"false\")\n\te.SetNamedLinkConditionFuncParams(\"g\", \"alice\", \"data5_admin\", \"not false\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data4\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data4\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data5\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data5\", \"write\", false)\n\n\tm, _ = model.NewModelFromFile(\"examples/rbac_with_domain_temporal_roles_model.conf\")\n\te, _ = NewEnforcer(m)\n\n\t_, _ = e.AddPolicies([][]string{\n\t\t{\"alice\", \"domain1\", \"data1\", \"read\"},\n\t\t{\"alice\", \"domain1\", \"data1\", \"write\"},\n\t\t{\"data2_admin\", \"domain2\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"domain2\", \"data2\", \"write\"},\n\t\t{\"data3_admin\", \"domain3\", \"data3\", \"read\"},\n\t\t{\"data3_admin\", \"domain3\", \"data3\", \"write\"},\n\t\t{\"data4_admin\", \"domain4\", \"data4\", \"read\"},\n\t\t{\"data4_admin\", \"domain4\", \"data4\", \"write\"},\n\t\t{\"data5_admin\", \"domain5\", \"data5\", \"read\"},\n\t\t{\"data5_admin\", \"domain5\", \"data5\", \"write\"},\n\t})\n\n\t_, _ = e.AddGroupingPolicies([][]string{\n\t\t{\"alice\", \"data2_admin\", \"domain2\", \"_\", \"_\"},\n\t\t{\"alice\", \"data3_admin\", \"domain3\", \"_\", \"_\"},\n\t\t{\"alice\", \"data4_admin\", \"domain4\", \"_\", \"_\"},\n\t\t{\"alice\", \"data5_admin\", \"domain5\", \"_\", \"_\"},\n\t})\n\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data2_admin\", \"domain2\", TrueFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data3_admin\", \"domain3\", TrueFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data4_admin\", \"domain4\", FalseFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data5_admin\", \"domain5\", FalseFunc)\n\n\te.SetNamedDomainLinkConditionFuncParams(\"g\", \"alice\", \"data2_admin\", \"domain2\", \"true\")\n\te.SetNamedDomainLinkConditionFuncParams(\"g\", \"alice\", \"data3_admin\", \"domain3\", \"not true\")\n\te.SetNamedDomainLinkConditionFuncParams(\"g\", \"alice\", \"data4_admin\", \"domain4\", \"false\")\n\te.SetNamedDomainLinkConditionFuncParams(\"g\", \"alice\", \"data5_admin\", \"domain5\", \"not false\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain3\", \"data3\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain3\", \"data3\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain4\", \"data4\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain4\", \"data4\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain5\", \"data5\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain5\", \"data5\", \"write\", false)\n}\n\nfunc TestEnforcerWithDefaultDetector(t *testing.T) {\n\t// Test that default detector is enabled and detects cycles\n\t_, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_with_cycle_policy.csv\")\n\n\t// Expect an error because the policy contains a cycle\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error when loading policy with cycle, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestEnforcerRunDetections(t *testing.T) {\n\t// Test explicit RunDetections() call\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t// Should not error on valid policy\n\terr := e.RunDetections()\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error when running detections on valid policy, but got: %v\", err)\n\t}\n\n\t// Now add a cycle manually\n\t_, _ = e.AddGroupingPolicy(\"alice\", \"data2_admin\")\n\t_, _ = e.AddGroupingPolicy(\"data2_admin\", \"super_admin\")\n\t_, _ = e.AddGroupingPolicy(\"super_admin\", \"alice\")\n\n\t// Should detect the cycle\n\terr = e.RunDetections()\n\tif err == nil {\n\t\tt.Error(\"Expected cycle detection error, but got nil\")\n\t} else {\n\t\terrMsg := err.Error()\n\t\tif !strings.Contains(errMsg, \"cycle detected\") {\n\t\t\tt.Errorf(\"Expected error message to contain 'cycle detected', got: %s\", errMsg)\n\t\t}\n\t}\n}\n\nfunc TestEnforcerSetDetector(t *testing.T) {\n\t// Test SetDetector() method\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t// Create a custom detector\n\tcustomDetector := detector.NewDefaultDetector()\n\te.SetDetector(customDetector)\n\n\t// Should still work with custom detector\n\terr := e.RunDetections()\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error with custom detector, but got: %v\", err)\n\t}\n}\n\nfunc TestEnforcerSetDetectors(t *testing.T) {\n\t// Test SetDetectors() method\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t// Create multiple detectors\n\tdetectors := []detector.Detector{\n\t\tdetector.NewDefaultDetector(),\n\t\tdetector.NewDefaultDetector(),\n\t}\n\te.SetDetectors(detectors)\n\n\t// Should work with multiple detectors\n\terr := e.RunDetections()\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error with multiple detectors, but got: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "enforcer_transactional.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v3/persist\"\n\t\"github.com/google/uuid\"\n)\n\n// TransactionalEnforcer extends Enforcer with transaction support.\n// It provides atomic policy operations through transactions.\ntype TransactionalEnforcer struct {\n\t*Enforcer                     // Embedded enforcer for all standard functionality\n\tactiveTransactions sync.Map   // Stores active transactions.\n\tmodelVersion       int64      // Model version number for optimistic locking.\n\tcommitLock         sync.Mutex // Protects commit and rollback operations.\n}\n\n// NewTransactionalEnforcer creates a new TransactionalEnforcer.\n// It accepts the same parameters as NewEnforcer.\nfunc NewTransactionalEnforcer(params ...interface{}) (*TransactionalEnforcer, error) {\n\tenforcer, err := NewEnforcer(params...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &TransactionalEnforcer{\n\t\tEnforcer: enforcer,\n\t}, nil\n}\n\n// BeginTransaction starts a new transaction.\n// Returns an error if a transaction is already in progress or if the adapter doesn't support transactions.\nfunc (te *TransactionalEnforcer) BeginTransaction(ctx context.Context) (*Transaction, error) {\n\t// Check if adapter supports transactions.\n\ttxAdapter, ok := te.adapter.(persist.TransactionalAdapter)\n\tif !ok {\n\t\treturn nil, errors.New(\"adapter does not support transactions\")\n\t}\n\n\t// Start database transaction.\n\ttxContext, err := txAdapter.BeginTransaction(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Create transaction buffer with current model snapshot.\n\tbuffer := NewTransactionBuffer(te.model)\n\n\ttx := &Transaction{\n\t\tid:          uuid.New().String(),\n\t\tenforcer:    te,\n\t\tbuffer:      buffer,\n\t\ttxContext:   txContext,\n\t\tctx:         ctx,\n\t\tbaseVersion: atomic.LoadInt64(&te.modelVersion),\n\t\tstartTime:   time.Now(),\n\t}\n\n\tte.activeTransactions.Store(tx.id, tx)\n\treturn tx, nil\n}\n\n// GetTransaction returns a transaction by its ID, or nil if not found.\nfunc (te *TransactionalEnforcer) GetTransaction(id string) *Transaction {\n\tif tx, ok := te.activeTransactions.Load(id); ok {\n\t\treturn tx.(*Transaction)\n\t}\n\treturn nil\n}\n\n// IsTransactionActive returns true if the transaction with the given ID is active.\nfunc (te *TransactionalEnforcer) IsTransactionActive(id string) bool {\n\tif tx := te.GetTransaction(id); tx != nil {\n\t\treturn tx.IsActive()\n\t}\n\treturn false\n}\n\n// WithTransaction executes a function within a transaction.\n// If the function returns an error, the transaction is rolled back.\n// Otherwise, it's committed automatically.\nfunc (te *TransactionalEnforcer) WithTransaction(ctx context.Context, fn func(*Transaction) error) error {\n\ttx, err := te.BeginTransaction(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\t_ = tx.Rollback()\n\t\t\tpanic(r)\n\t\t}\n\t}()\n\n\terr = fn(tx)\n\tif err != nil {\n\t\t_ = tx.Rollback()\n\t\treturn err\n\t}\n\n\treturn tx.Commit()\n}\n"
  },
  {
    "path": "error_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n)\n\nfunc TestPathError(t *testing.T) {\n\t_, err := NewEnforcer(\"hope_this_path_wont_exist\", \"\")\n\tif err == nil {\n\t\tt.Errorf(\"Should be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n}\n\nfunc TestEnforcerParamError(t *testing.T) {\n\t_, err := NewEnforcer(1, 2, 3)\n\tif err == nil {\n\t\tt.Errorf(\"Should not be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\t_, err2 := NewEnforcer(1, \"2\")\n\tif err2 == nil {\n\t\tt.Errorf(\"Should not be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err2.Error())\n\t}\n}\n\nfunc TestModelError(t *testing.T) {\n\t_, err := NewEnforcer(\"examples/error/error_model.conf\", \"examples/error/error_policy.csv\")\n\tif err == nil {\n\t\tt.Errorf(\"Should be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n}\n\n// func TestPolicyError(t *testing.T) {\n//\t_, err := NewEnforcer(\"examples/basic_model.conf\", \"examples/error/error_policy.csv\")\n//\tif err == nil {\n//\t\tt.Errorf(\"Should be error here.\")\n//\t} else {\n//\t\tt.Log(\"Test on error: \")\n//\t\tt.Log(err.Error())\n//\t}\n//}\n\nfunc TestEnforceError(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\t_, err := e.Enforce(\"wrong\", \"wrong\")\n\tif err == nil {\n\t\tt.Errorf(\"Should be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\te, _ = NewEnforcer(\"examples/abac_rule_model.conf\")\n\t_, err = e.Enforce(\"wang\", \"wang\", \"wang\")\n\tif err == nil {\n\t\tt.Errorf(\"Should be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n}\n\nfunc TestNoError(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\terr := e.LoadModel()\n\tif err != nil {\n\t\tt.Errorf(\"Should be no error here.\")\n\t\tt.Log(\"Unexpected error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\terr = e.LoadPolicy()\n\tif err != nil {\n\t\tt.Errorf(\"Should be no error here.\")\n\t\tt.Log(\"Unexpected error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\terr = e.SavePolicy()\n\tif err != nil {\n\t\tt.Errorf(\"Should be no error here.\")\n\t\tt.Log(\"Unexpected error: \")\n\t\tt.Log(err.Error())\n\t}\n}\n\nfunc TestModelNoError(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\te.modelPath = \"hope_this_path_wont_exist\"\n\terr := e.LoadModel()\n\n\tif err == nil {\n\t\tt.Errorf(\"Should be error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n}\n\nfunc TestMockAdapterErrors(t *testing.T) {\n\tadapter := fileadapter.NewAdapterMock(\"examples/rbac_with_domains_policy.csv\")\n\tadapter.SetMockErr(\"mock error\")\n\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\tadded, err := e.AddPolicy(\"admin\", \"domain3\", \"data1\", \"read\")\n\tif added {\n\t\tt.Errorf(\"added should be false\")\n\t}\n\n\tif err == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\trules := [][]string{\n\t\t{\"admin\", \"domain4\", \"data1\", \"read\"},\n\t}\n\tadded, err = e.AddPolicies(rules)\n\tif added {\n\t\tt.Errorf(\"added should be false\")\n\t}\n\n\tif err == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\tremoved, err2 := e.RemoveFilteredPolicy(1, \"domain1\", \"data1\")\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err2 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err2.Error())\n\t}\n\n\tremoved, err3 := e.RemovePolicy(\"admin\", \"domain2\", \"data2\", \"read\")\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err3 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err3.Error())\n\t}\n\n\trules = [][]string{\n\t\t{\"admin\", \"domain1\", \"data1\", \"read\"},\n\t}\n\tremoved, err = e.RemovePolicies(rules)\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err.Error())\n\t}\n\n\tadded, err4 := e.AddGroupingPolicy(\"bob\", \"admin2\")\n\tif added {\n\t\tt.Errorf(\"added should be false\")\n\t}\n\n\tif err4 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err4.Error())\n\t}\n\n\tadded, err5 := e.AddNamedGroupingPolicy(\"g\", []string{\"eve\", \"admin2\", \"domain1\"})\n\tif added {\n\t\tt.Errorf(\"added should be false\")\n\t}\n\n\tif err5 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err5.Error())\n\t}\n\n\tadded, err6 := e.AddNamedPolicy(\"p\", []string{\"admin2\", \"domain2\", \"data2\", \"write\"})\n\tif added {\n\t\tt.Errorf(\"added should be false\")\n\t}\n\n\tif err6 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err6.Error())\n\t}\n\n\tremoved, err7 := e.RemoveGroupingPolicy(\"bob\", \"admin2\")\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err7 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err7.Error())\n\t}\n\n\tremoved, err8 := e.RemoveFilteredGroupingPolicy(0, \"bob\")\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err8 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err8.Error())\n\t}\n\n\tremoved, err9 := e.RemoveNamedGroupingPolicy(\"g\", []string{\"alice\", \"admin\", \"domain1\"})\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err9 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err9.Error())\n\t}\n\n\tremoved, err10 := e.RemoveFilteredNamedGroupingPolicy(\"g\", 0, \"eve\")\n\tif removed {\n\t\tt.Errorf(\"removed should be false\")\n\t}\n\n\tif err10 == nil {\n\t\tt.Errorf(\"Should be an error here.\")\n\t} else {\n\t\tt.Log(\"Test on error: \")\n\t\tt.Log(err10.Error())\n\t}\n}\n"
  },
  {
    "path": "errors/constraint_errors.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// Global errors for constraints defined here.\nvar (\n\tErrConstraintViolation         = errors.New(\"constraint violation\")\n\tErrConstraintParsingError      = errors.New(\"constraint parsing error\")\n\tErrConstraintRequiresRBAC      = errors.New(\"constraints require RBAC to be enabled (role_definition section must exist)\")\n\tErrInvalidConstraintDefinition = errors.New(\"invalid constraint definition\")\n)\n\n// ConstraintViolationError represents a specific constraint violation.\ntype ConstraintViolationError struct {\n\tConstraintName string\n\tMessage        string\n}\n\nfunc (e *ConstraintViolationError) Error() string {\n\treturn fmt.Sprintf(\"constraint violation [%s]: %s\", e.ConstraintName, e.Message)\n}\n\n// NewConstraintViolationError creates a new constraint violation error.\nfunc NewConstraintViolationError(constraintName, message string) error {\n\treturn &ConstraintViolationError{\n\t\tConstraintName: constraintName,\n\t\tMessage:        message,\n\t}\n}\n"
  },
  {
    "path": "errors/rbac_errors.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage errors\n\nimport \"errors\"\n\n// Global errors for rbac defined here.\nvar (\n\tErrNameNotFound                = errors.New(\"error: name does not exist\")\n\tErrDomainParameter             = errors.New(\"error: domain should be 1 parameter\")\n\tErrLinkNotFound                = errors.New(\"error: link between name1 and name2 does not exist\")\n\tErrUseDomainParameter          = errors.New(\"error: useDomain should be 1 parameter\")\n\tErrInvalidFieldValuesParameter = errors.New(\"fieldValues requires at least one parameter\")\n\n\t// GetAllowedObjectConditions errors.\n\tErrObjCondition   = errors.New(\"need to meet the prefix required by the object condition\")\n\tErrEmptyCondition = errors.New(\"GetAllowedObjectConditions have an empty condition\")\n)\n"
  },
  {
    "path": "examples/abac_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == r.obj.Owner"
  },
  {
    "path": "examples/abac_not_using_policy_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act, eft\n\n[policy_effect]\ne = some(where (p.eft == allow)) && !some(where (p.eft == deny))\n\n[matchers]\nm = r.sub == r.obj.Owner"
  },
  {
    "path": "examples/abac_rule_effect_policy.csv",
    "content": "p, alice, /data1, read, deny\np, alice, /data1, write, allow\np, bob, /data2, write, deny\np, bob, /data2, read, allow"
  },
  {
    "path": "examples/abac_rule_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub_rule, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/abac_rule_policy.csv",
    "content": "p, r.sub.Age > 18, /data1, read\np, r.sub.Age < 60, /data2, write"
  },
  {
    "path": "examples/basic_inverse_policy.csv",
    "content": "p, alice, data1, write\np, bob, data2, read"
  },
  {
    "path": "examples/basic_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/basic_model_without_spaces.conf",
    "content": "[request_definition]\nr = sub,obj,act\n\n[policy_definition]\np = sub,obj,act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/basic_policy.csv",
    "content": "p, alice, data1, read\np, bob, data2, write"
  },
  {
    "path": "examples/basic_with_root_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == \"root\""
  },
  {
    "path": "examples/basic_without_resources_model.conf",
    "content": "[request_definition]\nr = sub, act\n\n[policy_definition]\np = sub, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.act == p.act"
  },
  {
    "path": "examples/basic_without_resources_policy.csv",
    "content": "p, alice, read\np, bob, write"
  },
  {
    "path": "examples/basic_without_users_model.conf",
    "content": "[request_definition]\nr = obj, act\n\n[policy_definition]\np = obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/basic_without_users_policy.csv",
    "content": "p, data1, read\np, data2, write"
  },
  {
    "path": "examples/biba_model.conf",
    "content": "[request_definition]\nr = sub, sub_level, obj, obj_level, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm =(r.act == \"read\"  && r.sub_level <= r.obj_level) || (r.act == \"write\" && r.sub_level >= r.obj_level)"
  },
  {
    "path": "examples/blp_model.conf",
    "content": "[request_definition]\nr = sub, sub_level, obj, obj_level, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = (r.act == \"read\" && r.sub_level >= r.obj_level) || (r.act == \"write\" && r.sub_level <= r.obj_level) "
  },
  {
    "path": "examples/comment_model.conf",
    "content": "[request_definition]\nr = sub, obj, act ; Request definition\n\n[policy_definition]\np = sub, obj, act \n\n[policy_effect]\ne = some(where (p.eft == allow)) # This is policy effect.\n\n# Matchers\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act  "
  },
  {
    "path": "examples/error/error_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/error/error_policy.csv",
    "content": "p, alice, data1, read\nbob, data2, write"
  },
  {
    "path": "examples/eval_operator_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub_rule, obj_rule, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = eval(p.sub_rule) && eval(p.obj_rule) && (p.act == '*' || r.act == p.act)\n"
  },
  {
    "path": "examples/eval_operator_policy.csv",
    "content": "p, r.sub == 'admin' || false, r.obj == 'users', write"
  },
  {
    "path": "examples/glob_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && globMatch(r.obj, p.obj) && r.act == p.act"
  },
  {
    "path": "examples/glob_policy.csv",
    "content": "p, u1, /foo/*, read\np, u2, /foo*, read\np, u3, /*/foo/*, read\np, u4, *, read"
  },
  {
    "path": "examples/ipmatch_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/ipmatch_policy.csv",
    "content": "p, 192.168.2.0/24, data1, read\np, 10.0.0.0/16, data2, write"
  },
  {
    "path": "examples/keyget2_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && keyGet2(r.obj, p.obj, 'resource') in ('age', 'name') && regexMatch(r.act, p.act)\n"
  },
  {
    "path": "examples/keyget_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && (r.obj == p.obj || keyGet(r.obj, p.obj) in ('age','name')) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/keymatch2_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/keymatch2_policy.csv",
    "content": "p, alice, /alice_data/:resource, GET\np, alice, /alice_data2/:id/using/:resId, GET"
  },
  {
    "path": "examples/keymatch_custom_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && keyMatchCustom(r.obj, p.obj) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/keymatch_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/keymatch_policy.csv",
    "content": "p, alice, /alice_data/*, GET\np, alice, /alice_data/resource1, POST\n\np, bob, /alice_data/resource2, GET\np, bob, /bob_data/*, POST\n\np, cathy, /cathy_data, (GET)|(POST)"
  },
  {
    "path": "examples/keymatch_with_rbac_in_domain.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)\n"
  },
  {
    "path": "examples/keymatch_with_rbac_in_domain.csv",
    "content": "g, can_manage, can_use, *\n\np, can_manage, engines/*, *, (pause)|(resume)\np, can_use, engines/*, *, (attach)|(detach)\n\ng, Username==test2, can_manage, engines/engine1\n"
  },
  {
    "path": "examples/lbac_model.conf",
    "content": "[request_definition]\nr = sub, subject_confidentiality, subject_integrity, obj, object_confidentiality, object_integrity, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = (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)"
  },
  {
    "path": "examples/multiple_policy_definitions_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\nr2 = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\np2= sub_rule, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\n#RABC\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n#ABAC\nm2 = eval(p2.sub_rule) && r2.obj == p2.obj && r2.act == p2.act\n"
  },
  {
    "path": "examples/multiple_policy_definitions_policy.csv",
    "content": "p, data2_admin, data2, read\np2, r2.sub.Age > 18 && r2.sub.Age < 60, /data1, read, allow\np2, r2.sub.Age > 60 && r2.sub.Age < 100, /data1, read, deny\n\ng, alice, data2_admin"
  },
  {
    "path": "examples/object_conditions_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, sub_rule, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && eval(p.sub_rule) && r.act == p.act"
  },
  {
    "path": "examples/object_conditions_policy.csv",
    "content": "p, alice, r.obj.price < 25, read\np, admin, r.obj.category_id = 2, read\np, bob, r.obj.author = bob, write\n\ng, alice, admin"
  },
  {
    "path": "examples/orbac_model.conf",
    "content": "[request_definition]\nr = sub, org, obj, act\n\n[policy_definition]\np = role, activity, view, org\n\n[role_definition]\ng = _, _, _\ng2 = _, _, _\ng3 = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = 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\n"
  },
  {
    "path": "examples/orbac_policy.csv",
    "content": "# Permission rules: role, activity, view, organization\np, manager, modify, document, org1\np, manager, consult, document, org1\np, employee, consult, document, org1\np, manager, modify, report, org2\np, manager, consult, report, org2\np, employee, consult, report, org2\n\n# Empower: subject, role, organization\ng, alice, manager, org1\ng, bob, employee, org1\ng, charlie, manager, org2\ng, david, employee, org2\n\n# Use: action, activity, organization\ng2, write, modify, org1\ng2, read, consult, org1\ng2, write, modify, org2\ng2, read, consult, org2\n\n# Consider: object, view, organization\ng3, data1, document, org1\ng3, data2, document, org1\ng3, report1, report, org2\ng3, report2, report, org2\n"
  },
  {
    "path": "examples/pbac_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub_rule, obj_rule, act\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = eval(p.sub_rule) && eval(p.obj_rule) && r.act == p.act"
  },
  {
    "path": "examples/pbac_policy.csv",
    "content": "p, r.sub.Role == 'admin', r.obj.Type == 'doc', read\np, r.sub.Age >= 18, r.obj.Type == 'video', play\n"
  },
  {
    "path": "examples/performance/rbac_with_pattern_large_scale_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.obj) && keyMatch4(r.obj, p.obj) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/performance/rbac_with_pattern_large_scale_policy.csv",
    "content": "# 132 policies / 3000 grouping policies / 300 subjuects / 6 roles\n# Policy - staff001\np, staff001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, staff001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, staff001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, staff001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, staff001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, staff001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, staff001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, staff001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, staff001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, staff001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, staff001,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, staff001,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, staff001,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, staff001,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, staff001,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, staff001,             /orgs/{orgID}/sites,          App003.*.Action3001\np, staff001,             /orgs/{orgID}/sites,          App003.*.Action3002\np, staff001,             /orgs/{orgID}/sites,          App003.*.Action3003\np, staff001,             /orgs/{orgID}/sites,          App003.*.Action3004\np, staff001,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, staff001,             /orgs/{orgID},                App004.*\np, staff001,             /orgs,                        App005.*\n\n# Policy - staff002\np, staff002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, staff002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, staff002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, staff002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, staff002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, staff002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, staff002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, staff002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, staff002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, staff002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, staff002,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, staff002,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, staff002,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, staff002,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, staff002,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, staff002,             /orgs/{orgID}/sites,          App003.*.Action3001\np, staff002,             /orgs/{orgID}/sites,          App003.*.Action3002\np, staff002,             /orgs/{orgID}/sites,          App003.*.Action3003\np, staff002,             /orgs/{orgID}/sites,          App003.*.Action3004\np, staff002,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, staff002,             /orgs/{orgID},                App004.*\np, staff002,             /orgs,                        App005.*\n\n# Policy - manager001\np, manager001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, manager001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, manager001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, manager001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, manager001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, manager001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, manager001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, manager001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, manager001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, manager001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, manager001,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, manager001,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, manager001,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, manager001,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, manager001,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, manager001,             /orgs/{orgID}/sites,          App003.*.Action3001\np, manager001,             /orgs/{orgID}/sites,          App003.*.Action3002\np, manager001,             /orgs/{orgID}/sites,          App003.*.Action3003\np, manager001,             /orgs/{orgID}/sites,          App003.*.Action3004\np, manager001,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, manager001,             /orgs/{orgID},                App004.*\np, manager001,             /orgs,                        App005.*\n\n# Policy - manager002\np, manager002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, manager002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, manager002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, manager002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, manager002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, manager002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, manager002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, manager002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, manager002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, manager002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, manager002,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, manager002,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, manager002,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, manager002,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, manager002,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, manager002,             /orgs/{orgID}/sites,          App003.*.Action3001\np, manager002,             /orgs/{orgID}/sites,          App003.*.Action3002\np, manager002,             /orgs/{orgID}/sites,          App003.*.Action3003\np, manager002,             /orgs/{orgID}/sites,          App003.*.Action3004\np, manager002,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, manager002,             /orgs/{orgID},                App004.*\np, manager002,             /orgs,                        App005.*\n\n# Policy - customer001\np, customer001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, customer001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, customer001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, customer001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, customer001,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, customer001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, customer001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, customer001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, customer001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, customer001,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, customer001,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, customer001,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, customer001,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, customer001,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, customer001,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, customer001,             /orgs/{orgID}/sites,          App003.*.Action3001\np, customer001,             /orgs/{orgID}/sites,          App003.*.Action3002\np, customer001,             /orgs/{orgID}/sites,          App003.*.Action3003\np, customer001,             /orgs/{orgID}/sites,          App003.*.Action3004\np, customer001,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, customer001,             /orgs/{orgID},                App004.*\np, customer001,             /orgs,                        App005.*\n\n# Policy - customer002\np, customer002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001\np, customer002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002\np, customer002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003\np, customer002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004\np, customer002,             /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005\np, customer002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2001\np, customer002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2002\np, customer002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2003\np, customer002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2004\np, customer002,             /orgs/{orgID}/sites/{siteID}, App002.*.Action2005\n\np, customer002,             /orgs/{orgID}/sites,          App001.Module002.Action1006\np, customer002,             /orgs/{orgID}/sites,          App001.Module002.Action1007\np, customer002,             /orgs/{orgID}/sites,          App001.Module002.Action1008\np, customer002,             /orgs/{orgID}/sites,          App001.Module002.Action1009\np, customer002,             /orgs/{orgID}/sites,          App001.Module002.Action1010\np, customer002,             /orgs/{orgID}/sites,          App003.*.Action3001\np, customer002,             /orgs/{orgID}/sites,          App003.*.Action3002\np, customer002,             /orgs/{orgID}/sites,          App003.*.Action3003\np, customer002,             /orgs/{orgID}/sites,          App003.*.Action3004\np, customer002,             /orgs/{orgID}/sites,          App003.*.Action3005\n\np, customer002,             /orgs/{orgID},                App004.*\np, customer002,             /orgs,                        App005.*\n\n# Group - staff001,  / org1\ng, staffUser1001, staff001, /orgs/1/sites/site001\ng, staffUser1001, staff001, /orgs/1/sites/site002\ng, staffUser1001, staff001, /orgs/1/sites/site003\ng, staffUser1001, staff001, /orgs/1/sites/site004\ng, staffUser1001, staff001, /orgs/1/sites/site005\n\ng, staffUser1001, staff001, /orgs/1/sites/site001\ng, staffUser1001, staff001, /orgs/1/sites/site002\ng, staffUser1001, staff001, /orgs/1/sites/site003\ng, staffUser1001, staff001, /orgs/1/sites/site004\ng, staffUser1001, staff001, /orgs/1/sites/site005\n\ng, staffUser1003, staff001, /orgs/1/sites/site001\ng, staffUser1003, staff001, /orgs/1/sites/site002\ng, staffUser1003, staff001, /orgs/1/sites/site003\ng, staffUser1003, staff001, /orgs/1/sites/site004\ng, staffUser1003, staff001, /orgs/1/sites/site005\n\ng, staffUser1004, staff001, /orgs/1/sites/site001\ng, staffUser1004, staff001, /orgs/1/sites/site002\ng, staffUser1004, staff001, /orgs/1/sites/site003\ng, staffUser1004, staff001, /orgs/1/sites/site004\ng, staffUser1004, staff001, /orgs/1/sites/site005\n\ng, staffUser1005, staff001, /orgs/1/sites/site001\ng, staffUser1005, staff001, /orgs/1/sites/site002\ng, staffUser1005, staff001, /orgs/1/sites/site003\ng, staffUser1005, staff001, /orgs/1/sites/site004\ng, staffUser1005, staff001, /orgs/1/sites/site005\n\ng, staffUser1006, staff001, /orgs/1/sites/site001\ng, staffUser1006, staff001, /orgs/1/sites/site002\ng, staffUser1006, staff001, /orgs/1/sites/site003\ng, staffUser1006, staff001, /orgs/1/sites/site004\ng, staffUser1006, staff001, /orgs/1/sites/site005\n\ng, staffUser1007, staff001, /orgs/1/sites/site001\ng, staffUser1007, staff001, /orgs/1/sites/site002\ng, staffUser1007, staff001, /orgs/1/sites/site003\ng, staffUser1007, staff001, /orgs/1/sites/site004\ng, staffUser1007, staff001, /orgs/1/sites/site005\n\ng, staffUser1008,  staff001, /orgs/1/sites/site001\ng, staffUser1008,  staff001, /orgs/1/sites/site002\ng, staffUser1008,  staff001, /orgs/1/sites/site003\ng, staffUser1008,  staff001, /orgs/1/sites/site004\ng, staffUser1008,  staff001, /orgs/1/sites/site005\n\ng, staffUser1009,  staff001, /orgs/1/sites/site001\ng, staffUser1009,  staff001, /orgs/1/sites/site002\ng, staffUser1009,  staff001, /orgs/1/sites/site003\ng, staffUser1009,  staff001, /orgs/1/sites/site004\ng, staffUser1009,  staff001, /orgs/1/sites/site005\n\ng, staffUser1010,  staff001, /orgs/1/sites/site001\ng, staffUser1010,  staff001, /orgs/1/sites/site002\ng, staffUser1010,  staff001, /orgs/1/sites/site003\ng, staffUser1010,  staff001, /orgs/1/sites/site004\ng, staffUser1010,  staff001, /orgs/1/sites/site005\n\ng, staffUser1011,  staff001, /orgs/1/sites/site001\ng, staffUser1011,  staff001, /orgs/1/sites/site002\ng, staffUser1011,  staff001, /orgs/1/sites/site003\ng, staffUser1011,  staff001, /orgs/1/sites/site004\ng, staffUser1011,  staff001, /orgs/1/sites/site005\n\ng, staffUser1012,  staff001, /orgs/1/sites/site001\ng, staffUser1012,  staff001, /orgs/1/sites/site002\ng, staffUser1012,  staff001, /orgs/1/sites/site003\ng, staffUser1012,  staff001, /orgs/1/sites/site004\ng, staffUser1012,  staff001, /orgs/1/sites/site005\n\ng, staffUser1013, staff001, /orgs/1/sites/site001\ng, staffUser1013, staff001, /orgs/1/sites/site002\ng, staffUser1013, staff001, /orgs/1/sites/site003\ng, staffUser1013, staff001, /orgs/1/sites/site004\ng, staffUser1013, staff001, /orgs/1/sites/site005\n\ng, staffUser1014, staff001, /orgs/1/sites/site001\ng, staffUser1014, staff001, /orgs/1/sites/site002\ng, staffUser1014, staff001, /orgs/1/sites/site003\ng, staffUser1014, staff001, /orgs/1/sites/site004\ng, staffUser1014, staff001, /orgs/1/sites/site005\n\ng, staffUser1015, staff001, /orgs/1/sites/site001\ng, staffUser1015, staff001, /orgs/1/sites/site002\ng, staffUser1015, staff001, /orgs/1/sites/site003\ng, staffUser1015, staff001, /orgs/1/sites/site004\ng, staffUser1015, staff001, /orgs/1/sites/site005\n\ng, staffUser1016, staff001, /orgs/1/sites/site001\ng, staffUser1016, staff001, /orgs/1/sites/site002\ng, staffUser1016, staff001, /orgs/1/sites/site003\ng, staffUser1016, staff001, /orgs/1/sites/site004\ng, staffUser1016, staff001, /orgs/1/sites/site005\n\ng, staffUser1017, staff001, /orgs/1/sites/site001\ng, staffUser1017, staff001, /orgs/1/sites/site002\ng, staffUser1017, staff001, /orgs/1/sites/site003\ng, staffUser1017, staff001, /orgs/1/sites/site004\ng, staffUser1017, staff001, /orgs/1/sites/site005\n\ng, staffUser1018,  staff001, /orgs/1/sites/site001\ng, staffUser1018,  staff001, /orgs/1/sites/site002\ng, staffUser1018,  staff001, /orgs/1/sites/site003\ng, staffUser1018,  staff001, /orgs/1/sites/site004\ng, staffUser1018,  staff001, /orgs/1/sites/site005\n\ng, staffUser1019,  staff001, /orgs/1/sites/site001\ng, staffUser1019,  staff001, /orgs/1/sites/site002\ng, staffUser1019,  staff001, /orgs/1/sites/site003\ng, staffUser1019,  staff001, /orgs/1/sites/site004\ng, staffUser1019,  staff001, /orgs/1/sites/site005\n\ng, staffUser1020,  staff001, /orgs/1/sites/site001\ng, staffUser1020,  staff001, /orgs/1/sites/site002\ng, staffUser1020,  staff001, /orgs/1/sites/site003\ng, staffUser1020,  staff001, /orgs/1/sites/site004\ng, staffUser1020,  staff001, /orgs/1/sites/site005\n\ng, staffUser1021,  staff001, /orgs/1/sites/site001\ng, staffUser1021,  staff001, /orgs/1/sites/site002\ng, staffUser1021,  staff001, /orgs/1/sites/site003\ng, staffUser1021,  staff001, /orgs/1/sites/site004\ng, staffUser1021,  staff001, /orgs/1/sites/site005\n\ng, staffUser1022,  staff001, /orgs/1/sites/site001\ng, staffUser1022,  staff001, /orgs/1/sites/site002\ng, staffUser1022,  staff001, /orgs/1/sites/site003\ng, staffUser1022,  staff001, /orgs/1/sites/site004\ng, staffUser1022,  staff001, /orgs/1/sites/site005\n\ng, staffUser1023, staff001, /orgs/1/sites/site001\ng, staffUser1023, staff001, /orgs/1/sites/site002\ng, staffUser1023, staff001, /orgs/1/sites/site003\ng, staffUser1023, staff001, /orgs/1/sites/site004\ng, staffUser1023, staff001, /orgs/1/sites/site005\n\ng, staffUser1024, staff001, /orgs/1/sites/site001\ng, staffUser1024, staff001, /orgs/1/sites/site002\ng, staffUser1024, staff001, /orgs/1/sites/site003\ng, staffUser1024, staff001, /orgs/1/sites/site004\ng, staffUser1024, staff001, /orgs/1/sites/site005\n\ng, staffUser1025, staff001, /orgs/1/sites/site001\ng, staffUser1025, staff001, /orgs/1/sites/site002\ng, staffUser1025, staff001, /orgs/1/sites/site003\ng, staffUser1025, staff001, /orgs/1/sites/site004\ng, staffUser1025, staff001, /orgs/1/sites/site005\n\ng, staffUser1026, staff001, /orgs/1/sites/site001\ng, staffUser1026, staff001, /orgs/1/sites/site002\ng, staffUser1026, staff001, /orgs/1/sites/site003\ng, staffUser1026, staff001, /orgs/1/sites/site004\ng, staffUser1026, staff001, /orgs/1/sites/site005\n\ng, staffUser1027, staff001, /orgs/1/sites/site001\ng, staffUser1027, staff001, /orgs/1/sites/site002\ng, staffUser1027, staff001, /orgs/1/sites/site003\ng, staffUser1027, staff001, /orgs/1/sites/site004\ng, staffUser1027, staff001, /orgs/1/sites/site005\n\ng, staffUser1028,  staff001, /orgs/1/sites/site001\ng, staffUser1028,  staff001, /orgs/1/sites/site002\ng, staffUser1028,  staff001, /orgs/1/sites/site003\ng, staffUser1028,  staff001, /orgs/1/sites/site004\ng, staffUser1028,  staff001, /orgs/1/sites/site005\n\ng, staffUser1029,  staff001, /orgs/1/sites/site001\ng, staffUser1029,  staff001, /orgs/1/sites/site002\ng, staffUser1029,  staff001, /orgs/1/sites/site003\ng, staffUser1029,  staff001, /orgs/1/sites/site004\ng, staffUser1029,  staff001, /orgs/1/sites/site005\n\ng, staffUser1030,  staff001, /orgs/1/sites/site001\ng, staffUser1030,  staff001, /orgs/1/sites/site002\ng, staffUser1030,  staff001, /orgs/1/sites/site003\ng, staffUser1030,  staff001, /orgs/1/sites/site004\ng, staffUser1030,  staff001, /orgs/1/sites/site005\n\ng, staffUser1031,  staff001, /orgs/1/sites/site001\ng, staffUser1031,  staff001, /orgs/1/sites/site002\ng, staffUser1031,  staff001, /orgs/1/sites/site003\ng, staffUser1031,  staff001, /orgs/1/sites/site004\ng, staffUser1031,  staff001, /orgs/1/sites/site005\n\ng, staffUser1032,  staff001, /orgs/1/sites/site001\ng, staffUser1032,  staff001, /orgs/1/sites/site002\ng, staffUser1032,  staff001, /orgs/1/sites/site003\ng, staffUser1032,  staff001, /orgs/1/sites/site004\ng, staffUser1032,  staff001, /orgs/1/sites/site005\n\ng, staffUser1033, staff001, /orgs/1/sites/site001\ng, staffUser1033, staff001, /orgs/1/sites/site002\ng, staffUser1033, staff001, /orgs/1/sites/site003\ng, staffUser1033, staff001, /orgs/1/sites/site004\ng, staffUser1033, staff001, /orgs/1/sites/site005\n\ng, staffUser1034, staff001, /orgs/1/sites/site001\ng, staffUser1034, staff001, /orgs/1/sites/site002\ng, staffUser1034, staff001, /orgs/1/sites/site003\ng, staffUser1034, staff001, /orgs/1/sites/site004\ng, staffUser1034, staff001, /orgs/1/sites/site005\n\ng, staffUser1035, staff001, /orgs/1/sites/site001\ng, staffUser1035, staff001, /orgs/1/sites/site002\ng, staffUser1035, staff001, /orgs/1/sites/site003\ng, staffUser1035, staff001, /orgs/1/sites/site004\ng, staffUser1035, staff001, /orgs/1/sites/site005\n\ng, staffUser1036, staff001, /orgs/1/sites/site001\ng, staffUser1036, staff001, /orgs/1/sites/site002\ng, staffUser1036, staff001, /orgs/1/sites/site003\ng, staffUser1036, staff001, /orgs/1/sites/site004\ng, staffUser1036, staff001, /orgs/1/sites/site005\n\ng, staffUser1037, staff001, /orgs/1/sites/site001\ng, staffUser1037, staff001, /orgs/1/sites/site002\ng, staffUser1037, staff001, /orgs/1/sites/site003\ng, staffUser1037, staff001, /orgs/1/sites/site004\ng, staffUser1037, staff001, /orgs/1/sites/site005\n\ng, staffUser1038,  staff001, /orgs/1/sites/site001\ng, staffUser1038,  staff001, /orgs/1/sites/site002\ng, staffUser1038,  staff001, /orgs/1/sites/site003\ng, staffUser1038,  staff001, /orgs/1/sites/site004\ng, staffUser1038,  staff001, /orgs/1/sites/site005\n\ng, staffUser1039,  staff001, /orgs/1/sites/site001\ng, staffUser1039,  staff001, /orgs/1/sites/site002\ng, staffUser1039,  staff001, /orgs/1/sites/site003\ng, staffUser1039,  staff001, /orgs/1/sites/site004\ng, staffUser1039,  staff001, /orgs/1/sites/site005\n\ng, staffUser1040,  staff001, /orgs/1/sites/site001\ng, staffUser1040,  staff001, /orgs/1/sites/site002\ng, staffUser1040,  staff001, /orgs/1/sites/site003\ng, staffUser1040,  staff001, /orgs/1/sites/site004\ng, staffUser1040,  staff001, /orgs/1/sites/site005\n\ng, staffUser1041,  staff001, /orgs/1/sites/site001\ng, staffUser1041,  staff001, /orgs/1/sites/site002\ng, staffUser1041,  staff001, /orgs/1/sites/site003\ng, staffUser1041,  staff001, /orgs/1/sites/site004\ng, staffUser1041,  staff001, /orgs/1/sites/site005\n\ng, staffUser1042,  staff001, /orgs/1/sites/site001\ng, staffUser1042,  staff001, /orgs/1/sites/site002\ng, staffUser1042,  staff001, /orgs/1/sites/site003\ng, staffUser1042,  staff001, /orgs/1/sites/site004\ng, staffUser1042,  staff001, /orgs/1/sites/site005\n\ng, staffUser1043, staff001, /orgs/1/sites/site001\ng, staffUser1043, staff001, /orgs/1/sites/site002\ng, staffUser1043, staff001, /orgs/1/sites/site003\ng, staffUser1043, staff001, /orgs/1/sites/site004\ng, staffUser1043, staff001, /orgs/1/sites/site005\n\ng, staffUser1044, staff001, /orgs/1/sites/site001\ng, staffUser1044, staff001, /orgs/1/sites/site002\ng, staffUser1044, staff001, /orgs/1/sites/site003\ng, staffUser1044, staff001, /orgs/1/sites/site004\ng, staffUser1044, staff001, /orgs/1/sites/site005\n\ng, staffUser1045, staff001, /orgs/1/sites/site001\ng, staffUser1045, staff001, /orgs/1/sites/site002\ng, staffUser1045, staff001, /orgs/1/sites/site003\ng, staffUser1045, staff001, /orgs/1/sites/site004\ng, staffUser1045, staff001, /orgs/1/sites/site005\n\ng, staffUser1046, staff001, /orgs/1/sites/site001\ng, staffUser1046, staff001, /orgs/1/sites/site002\ng, staffUser1046, staff001, /orgs/1/sites/site003\ng, staffUser1046, staff001, /orgs/1/sites/site004\ng, staffUser1046, staff001, /orgs/1/sites/site005\n\ng, staffUser1047, staff001, /orgs/1/sites/site001\ng, staffUser1047, staff001, /orgs/1/sites/site002\ng, staffUser1047, staff001, /orgs/1/sites/site003\ng, staffUser1047, staff001, /orgs/1/sites/site004\ng, staffUser1047, staff001, /orgs/1/sites/site005\n\ng, staffUser1048,  staff001, /orgs/1/sites/site001\ng, staffUser1048,  staff001, /orgs/1/sites/site002\ng, staffUser1048,  staff001, /orgs/1/sites/site003\ng, staffUser1048,  staff001, /orgs/1/sites/site004\ng, staffUser1048,  staff001, /orgs/1/sites/site005\n\ng, staffUser1049,  staff001, /orgs/1/sites/site001\ng, staffUser1049,  staff001, /orgs/1/sites/site002\ng, staffUser1049,  staff001, /orgs/1/sites/site003\ng, staffUser1049,  staff001, /orgs/1/sites/site004\ng, staffUser1049,  staff001, /orgs/1/sites/site005\n\ng, staffUser1050,  staff001, /orgs/1/sites/site001\ng, staffUser1050,  staff001, /orgs/1/sites/site002\ng, staffUser1050,  staff001, /orgs/1/sites/site003\ng, staffUser1050,  staff001, /orgs/1/sites/site004\ng, staffUser1050,  staff001, /orgs/1/sites/site005\n\n# Group - staff001, / org1\ng, staffUser2001, staff001, /orgs/1/sites/site001\ng, staffUser2001, staff001, /orgs/1/sites/site002\ng, staffUser2001, staff001, /orgs/1/sites/site003\ng, staffUser2001, staff001, /orgs/1/sites/site004\ng, staffUser2001, staff001, /orgs/1/sites/site005\n\ng, staffUser2001, staff001, /orgs/1/sites/site001\ng, staffUser2001, staff001, /orgs/1/sites/site002\ng, staffUser2001, staff001, /orgs/1/sites/site003\ng, staffUser2001, staff001, /orgs/1/sites/site004\ng, staffUser2001, staff001, /orgs/1/sites/site005\n\ng, staffUser2003, staff001, /orgs/1/sites/site001\ng, staffUser2003, staff001, /orgs/1/sites/site002\ng, staffUser2003, staff001, /orgs/1/sites/site003\ng, staffUser2003, staff001, /orgs/1/sites/site004\ng, staffUser2003, staff001, /orgs/1/sites/site005\n\ng, staffUser2004, staff001, /orgs/1/sites/site001\ng, staffUser2004, staff001, /orgs/1/sites/site002\ng, staffUser2004, staff001, /orgs/1/sites/site003\ng, staffUser2004, staff001, /orgs/1/sites/site004\ng, staffUser2004, staff001, /orgs/1/sites/site005\n\ng, staffUser2005, staff001, /orgs/1/sites/site001\ng, staffUser2005, staff001, /orgs/1/sites/site002\ng, staffUser2005, staff001, /orgs/1/sites/site003\ng, staffUser2005, staff001, /orgs/1/sites/site004\ng, staffUser2005, staff001, /orgs/1/sites/site005\n\ng, staffUser2006, staff001, /orgs/1/sites/site001\ng, staffUser2006, staff001, /orgs/1/sites/site002\ng, staffUser2006, staff001, /orgs/1/sites/site003\ng, staffUser2006, staff001, /orgs/1/sites/site004\ng, staffUser2006, staff001, /orgs/1/sites/site005\n\ng, staffUser2007, staff001, /orgs/1/sites/site001\ng, staffUser2007, staff001, /orgs/1/sites/site002\ng, staffUser2007, staff001, /orgs/1/sites/site003\ng, staffUser2007, staff001, /orgs/1/sites/site004\ng, staffUser2007, staff001, /orgs/1/sites/site005\n\ng, staffUser2008,  staff001, /orgs/1/sites/site001\ng, staffUser2008,  staff001, /orgs/1/sites/site002\ng, staffUser2008,  staff001, /orgs/1/sites/site003\ng, staffUser2008,  staff001, /orgs/1/sites/site004\ng, staffUser2008,  staff001, /orgs/1/sites/site005\n\ng, staffUser2009,  staff001, /orgs/1/sites/site001\ng, staffUser2009,  staff001, /orgs/1/sites/site002\ng, staffUser2009,  staff001, /orgs/1/sites/site003\ng, staffUser2009,  staff001, /orgs/1/sites/site004\ng, staffUser2009,  staff001, /orgs/1/sites/site005\n\ng, staffUser2010,  staff001, /orgs/1/sites/site001\ng, staffUser2010,  staff001, /orgs/1/sites/site002\ng, staffUser2010,  staff001, /orgs/1/sites/site003\ng, staffUser2010,  staff001, /orgs/1/sites/site004\ng, staffUser2010,  staff001, /orgs/1/sites/site005\n\ng, staffUser2011,  staff001, /orgs/1/sites/site001\ng, staffUser2011,  staff001, /orgs/1/sites/site002\ng, staffUser2011,  staff001, /orgs/1/sites/site003\ng, staffUser2011,  staff001, /orgs/1/sites/site004\ng, staffUser2011,  staff001, /orgs/1/sites/site005\n\ng, staffUser2012,  staff001, /orgs/1/sites/site001\ng, staffUser2012,  staff001, /orgs/1/sites/site002\ng, staffUser2012,  staff001, /orgs/1/sites/site003\ng, staffUser2012,  staff001, /orgs/1/sites/site004\ng, staffUser2012,  staff001, /orgs/1/sites/site005\n\ng, staffUser2013, staff001, /orgs/1/sites/site001\ng, staffUser2013, staff001, /orgs/1/sites/site002\ng, staffUser2013, staff001, /orgs/1/sites/site003\ng, staffUser2013, staff001, /orgs/1/sites/site004\ng, staffUser2013, staff001, /orgs/1/sites/site005\n\ng, staffUser2014, staff001, /orgs/1/sites/site001\ng, staffUser2014, staff001, /orgs/1/sites/site002\ng, staffUser2014, staff001, /orgs/1/sites/site003\ng, staffUser2014, staff001, /orgs/1/sites/site004\ng, staffUser2014, staff001, /orgs/1/sites/site005\n\ng, staffUser2015, staff001, /orgs/1/sites/site001\ng, staffUser2015, staff001, /orgs/1/sites/site002\ng, staffUser2015, staff001, /orgs/1/sites/site003\ng, staffUser2015, staff001, /orgs/1/sites/site004\ng, staffUser2015, staff001, /orgs/1/sites/site005\n\ng, staffUser2016, staff001, /orgs/1/sites/site001\ng, staffUser2016, staff001, /orgs/1/sites/site002\ng, staffUser2016, staff001, /orgs/1/sites/site003\ng, staffUser2016, staff001, /orgs/1/sites/site004\ng, staffUser2016, staff001, /orgs/1/sites/site005\n\ng, staffUser2017, staff001, /orgs/1/sites/site001\ng, staffUser2017, staff001, /orgs/1/sites/site002\ng, staffUser2017, staff001, /orgs/1/sites/site003\ng, staffUser2017, staff001, /orgs/1/sites/site004\ng, staffUser2017, staff001, /orgs/1/sites/site005\n\ng, staffUser2018,  staff001, /orgs/1/sites/site001\ng, staffUser2018,  staff001, /orgs/1/sites/site002\ng, staffUser2018,  staff001, /orgs/1/sites/site003\ng, staffUser2018,  staff001, /orgs/1/sites/site004\ng, staffUser2018,  staff001, /orgs/1/sites/site005\n\ng, staffUser2019,  staff001, /orgs/1/sites/site001\ng, staffUser2019,  staff001, /orgs/1/sites/site002\ng, staffUser2019,  staff001, /orgs/1/sites/site003\ng, staffUser2019,  staff001, /orgs/1/sites/site004\ng, staffUser2019,  staff001, /orgs/1/sites/site005\n\ng, staffUser2020,  staff001, /orgs/1/sites/site001\ng, staffUser2020,  staff001, /orgs/1/sites/site002\ng, staffUser2020,  staff001, /orgs/1/sites/site003\ng, staffUser2020,  staff001, /orgs/1/sites/site004\ng, staffUser2020,  staff001, /orgs/1/sites/site005\n\ng, staffUser2021,  staff001, /orgs/1/sites/site001\ng, staffUser2021,  staff001, /orgs/1/sites/site002\ng, staffUser2021,  staff001, /orgs/1/sites/site003\ng, staffUser2021,  staff001, /orgs/1/sites/site004\ng, staffUser2021,  staff001, /orgs/1/sites/site005\n\ng, staffUser2022,  staff001, /orgs/1/sites/site001\ng, staffUser2022,  staff001, /orgs/1/sites/site002\ng, staffUser2022,  staff001, /orgs/1/sites/site003\ng, staffUser2022,  staff001, /orgs/1/sites/site004\ng, staffUser2022,  staff001, /orgs/1/sites/site005\n\ng, staffUser2023, staff001, /orgs/1/sites/site001\ng, staffUser2023, staff001, /orgs/1/sites/site002\ng, staffUser2023, staff001, /orgs/1/sites/site003\ng, staffUser2023, staff001, /orgs/1/sites/site004\ng, staffUser2023, staff001, /orgs/1/sites/site005\n\ng, staffUser2024, staff001, /orgs/1/sites/site001\ng, staffUser2024, staff001, /orgs/1/sites/site002\ng, staffUser2024, staff001, /orgs/1/sites/site003\ng, staffUser2024, staff001, /orgs/1/sites/site004\ng, staffUser2024, staff001, /orgs/1/sites/site005\n\ng, staffUser2025, staff001, /orgs/1/sites/site001\ng, staffUser2025, staff001, /orgs/1/sites/site002\ng, staffUser2025, staff001, /orgs/1/sites/site003\ng, staffUser2025, staff001, /orgs/1/sites/site004\ng, staffUser2025, staff001, /orgs/1/sites/site005\n\ng, staffUser2026, staff001, /orgs/1/sites/site001\ng, staffUser2026, staff001, /orgs/1/sites/site002\ng, staffUser2026, staff001, /orgs/1/sites/site003\ng, staffUser2026, staff001, /orgs/1/sites/site004\ng, staffUser2026, staff001, /orgs/1/sites/site005\n\ng, staffUser2027, staff001, /orgs/1/sites/site001\ng, staffUser2027, staff001, /orgs/1/sites/site002\ng, staffUser2027, staff001, /orgs/1/sites/site003\ng, staffUser2027, staff001, /orgs/1/sites/site004\ng, staffUser2027, staff001, /orgs/1/sites/site005\n\ng, staffUser2028,  staff001, /orgs/1/sites/site001\ng, staffUser2028,  staff001, /orgs/1/sites/site002\ng, staffUser2028,  staff001, /orgs/1/sites/site003\ng, staffUser2028,  staff001, /orgs/1/sites/site004\ng, staffUser2028,  staff001, /orgs/1/sites/site005\n\ng, staffUser2029,  staff001, /orgs/1/sites/site001\ng, staffUser2029,  staff001, /orgs/1/sites/site002\ng, staffUser2029,  staff001, /orgs/1/sites/site003\ng, staffUser2029,  staff001, /orgs/1/sites/site004\ng, staffUser2029,  staff001, /orgs/1/sites/site005\n\ng, staffUser2030,  staff001, /orgs/1/sites/site001\ng, staffUser2030,  staff001, /orgs/1/sites/site002\ng, staffUser2030,  staff001, /orgs/1/sites/site003\ng, staffUser2030,  staff001, /orgs/1/sites/site004\ng, staffUser2030,  staff001, /orgs/1/sites/site005\n\ng, staffUser2031,  staff001, /orgs/1/sites/site001\ng, staffUser2031,  staff001, /orgs/1/sites/site002\ng, staffUser2031,  staff001, /orgs/1/sites/site003\ng, staffUser2031,  staff001, /orgs/1/sites/site004\ng, staffUser2031,  staff001, /orgs/1/sites/site005\n\ng, staffUser2032,  staff001, /orgs/1/sites/site001\ng, staffUser2032,  staff001, /orgs/1/sites/site002\ng, staffUser2032,  staff001, /orgs/1/sites/site003\ng, staffUser2032,  staff001, /orgs/1/sites/site004\ng, staffUser2032,  staff001, /orgs/1/sites/site005\n\ng, staffUser2033, staff001, /orgs/1/sites/site001\ng, staffUser2033, staff001, /orgs/1/sites/site002\ng, staffUser2033, staff001, /orgs/1/sites/site003\ng, staffUser2033, staff001, /orgs/1/sites/site004\ng, staffUser2033, staff001, /orgs/1/sites/site005\n\ng, staffUser2034, staff001, /orgs/1/sites/site001\ng, staffUser2034, staff001, /orgs/1/sites/site002\ng, staffUser2034, staff001, /orgs/1/sites/site003\ng, staffUser2034, staff001, /orgs/1/sites/site004\ng, staffUser2034, staff001, /orgs/1/sites/site005\n\ng, staffUser2035, staff001, /orgs/1/sites/site001\ng, staffUser2035, staff001, /orgs/1/sites/site002\ng, staffUser2035, staff001, /orgs/1/sites/site003\ng, staffUser2035, staff001, /orgs/1/sites/site004\ng, staffUser2035, staff001, /orgs/1/sites/site005\n\ng, staffUser2036, staff001, /orgs/1/sites/site001\ng, staffUser2036, staff001, /orgs/1/sites/site002\ng, staffUser2036, staff001, /orgs/1/sites/site003\ng, staffUser2036, staff001, /orgs/1/sites/site004\ng, staffUser2036, staff001, /orgs/1/sites/site005\n\ng, staffUser2037, staff001, /orgs/1/sites/site001\ng, staffUser2037, staff001, /orgs/1/sites/site002\ng, staffUser2037, staff001, /orgs/1/sites/site003\ng, staffUser2037, staff001, /orgs/1/sites/site004\ng, staffUser2037, staff001, /orgs/1/sites/site005\n\ng, staffUser2038,  staff001, /orgs/1/sites/site001\ng, staffUser2038,  staff001, /orgs/1/sites/site002\ng, staffUser2038,  staff001, /orgs/1/sites/site003\ng, staffUser2038,  staff001, /orgs/1/sites/site004\ng, staffUser2038,  staff001, /orgs/1/sites/site005\n\ng, staffUser2039,  staff001, /orgs/1/sites/site001\ng, staffUser2039,  staff001, /orgs/1/sites/site002\ng, staffUser2039,  staff001, /orgs/1/sites/site003\ng, staffUser2039,  staff001, /orgs/1/sites/site004\ng, staffUser2039,  staff001, /orgs/1/sites/site005\n\ng, staffUser2040,  staff001, /orgs/1/sites/site001\ng, staffUser2040,  staff001, /orgs/1/sites/site002\ng, staffUser2040,  staff001, /orgs/1/sites/site003\ng, staffUser2040,  staff001, /orgs/1/sites/site004\ng, staffUser2040,  staff001, /orgs/1/sites/site005\n\ng, staffUser2041,  staff001, /orgs/1/sites/site001\ng, staffUser2041,  staff001, /orgs/1/sites/site002\ng, staffUser2041,  staff001, /orgs/1/sites/site003\ng, staffUser2041,  staff001, /orgs/1/sites/site004\ng, staffUser2041,  staff001, /orgs/1/sites/site005\n\ng, staffUser2042,  staff001, /orgs/1/sites/site001\ng, staffUser2042,  staff001, /orgs/1/sites/site002\ng, staffUser2042,  staff001, /orgs/1/sites/site003\ng, staffUser2042,  staff001, /orgs/1/sites/site004\ng, staffUser2042,  staff001, /orgs/1/sites/site005\n\ng, staffUser2043, staff001, /orgs/1/sites/site001\ng, staffUser2043, staff001, /orgs/1/sites/site002\ng, staffUser2043, staff001, /orgs/1/sites/site003\ng, staffUser2043, staff001, /orgs/1/sites/site004\ng, staffUser2043, staff001, /orgs/1/sites/site005\n\ng, staffUser2044, staff001, /orgs/1/sites/site001\ng, staffUser2044, staff001, /orgs/1/sites/site002\ng, staffUser2044, staff001, /orgs/1/sites/site003\ng, staffUser2044, staff001, /orgs/1/sites/site004\ng, staffUser2044, staff001, /orgs/1/sites/site005\n\ng, staffUser2045, staff001, /orgs/1/sites/site001\ng, staffUser2045, staff001, /orgs/1/sites/site002\ng, staffUser2045, staff001, /orgs/1/sites/site003\ng, staffUser2045, staff001, /orgs/1/sites/site004\ng, staffUser2045, staff001, /orgs/1/sites/site005\n\ng, staffUser2046, staff001, /orgs/1/sites/site001\ng, staffUser2046, staff001, /orgs/1/sites/site002\ng, staffUser2046, staff001, /orgs/1/sites/site003\ng, staffUser2046, staff001, /orgs/1/sites/site004\ng, staffUser2046, staff001, /orgs/1/sites/site005\n\ng, staffUser2047, staff001, /orgs/1/sites/site001\ng, staffUser2047, staff001, /orgs/1/sites/site002\ng, staffUser2047, staff001, /orgs/1/sites/site003\ng, staffUser2047, staff001, /orgs/1/sites/site004\ng, staffUser2047, staff001, /orgs/1/sites/site005\n\ng, staffUser2048,  staff001, /orgs/1/sites/site001\ng, staffUser2048,  staff001, /orgs/1/sites/site002\ng, staffUser2048,  staff001, /orgs/1/sites/site003\ng, staffUser2048,  staff001, /orgs/1/sites/site004\ng, staffUser2048,  staff001, /orgs/1/sites/site005\n\ng, staffUser2049,  staff001, /orgs/1/sites/site001\ng, staffUser2049,  staff001, /orgs/1/sites/site002\ng, staffUser2049,  staff001, /orgs/1/sites/site003\ng, staffUser2049,  staff001, /orgs/1/sites/site004\ng, staffUser2049,  staff001, /orgs/1/sites/site005\n\ng, staffUser2050,  staff001, /orgs/1/sites/site001\ng, staffUser2050,  staff001, /orgs/1/sites/site002\ng, staffUser2050,  staff001, /orgs/1/sites/site003\ng, staffUser2050,  staff001, /orgs/1/sites/site004\ng, staffUser2050,  staff001, /orgs/1/sites/site005\n\n# Group - manager001, / org1\ng, managerUser1001, manager001, /orgs/1/sites/site001\ng, managerUser1001, manager001, /orgs/1/sites/site002\ng, managerUser1001, manager001, /orgs/1/sites/site003\ng, managerUser1001, manager001, /orgs/1/sites/site004\ng, managerUser1001, manager001, /orgs/1/sites/site005\n\ng, managerUser1001, manager001, /orgs/1/sites/site001\ng, managerUser1001, manager001, /orgs/1/sites/site002\ng, managerUser1001, manager001, /orgs/1/sites/site003\ng, managerUser1001, manager001, /orgs/1/sites/site004\ng, managerUser1001, manager001, /orgs/1/sites/site005\n\ng, managerUser1003, manager001, /orgs/1/sites/site001\ng, managerUser1003, manager001, /orgs/1/sites/site002\ng, managerUser1003, manager001, /orgs/1/sites/site003\ng, managerUser1003, manager001, /orgs/1/sites/site004\ng, managerUser1003, manager001, /orgs/1/sites/site005\n\ng, managerUser1004, manager001, /orgs/1/sites/site001\ng, managerUser1004, manager001, /orgs/1/sites/site002\ng, managerUser1004, manager001, /orgs/1/sites/site003\ng, managerUser1004, manager001, /orgs/1/sites/site004\ng, managerUser1004, manager001, /orgs/1/sites/site005\n\ng, managerUser1005, manager001, /orgs/1/sites/site001\ng, managerUser1005, manager001, /orgs/1/sites/site002\ng, managerUser1005, manager001, /orgs/1/sites/site003\ng, managerUser1005, manager001, /orgs/1/sites/site004\ng, managerUser1005, manager001, /orgs/1/sites/site005\n\ng, managerUser1006, manager001, /orgs/1/sites/site001\ng, managerUser1006, manager001, /orgs/1/sites/site002\ng, managerUser1006, manager001, /orgs/1/sites/site003\ng, managerUser1006, manager001, /orgs/1/sites/site004\ng, managerUser1006, manager001, /orgs/1/sites/site005\n\ng, managerUser1007, manager001, /orgs/1/sites/site001\ng, managerUser1007, manager001, /orgs/1/sites/site002\ng, managerUser1007, manager001, /orgs/1/sites/site003\ng, managerUser1007, manager001, /orgs/1/sites/site004\ng, managerUser1007, manager001, /orgs/1/sites/site005\n\ng, managerUser1008,  manager001, /orgs/1/sites/site001\ng, managerUser1008,  manager001, /orgs/1/sites/site002\ng, managerUser1008,  manager001, /orgs/1/sites/site003\ng, managerUser1008,  manager001, /orgs/1/sites/site004\ng, managerUser1008,  manager001, /orgs/1/sites/site005\n\ng, managerUser1009,  manager001, /orgs/1/sites/site001\ng, managerUser1009,  manager001, /orgs/1/sites/site002\ng, managerUser1009,  manager001, /orgs/1/sites/site003\ng, managerUser1009,  manager001, /orgs/1/sites/site004\ng, managerUser1009,  manager001, /orgs/1/sites/site005\n\ng, managerUser1010,  manager001, /orgs/1/sites/site001\ng, managerUser1010,  manager001, /orgs/1/sites/site002\ng, managerUser1010,  manager001, /orgs/1/sites/site003\ng, managerUser1010,  manager001, /orgs/1/sites/site004\ng, managerUser1010,  manager001, /orgs/1/sites/site005\n\ng, managerUser1011,  manager001, /orgs/1/sites/site001\ng, managerUser1011,  manager001, /orgs/1/sites/site002\ng, managerUser1011,  manager001, /orgs/1/sites/site003\ng, managerUser1011,  manager001, /orgs/1/sites/site004\ng, managerUser1011,  manager001, /orgs/1/sites/site005\n\ng, managerUser1012,  manager001, /orgs/1/sites/site001\ng, managerUser1012,  manager001, /orgs/1/sites/site002\ng, managerUser1012,  manager001, /orgs/1/sites/site003\ng, managerUser1012,  manager001, /orgs/1/sites/site004\ng, managerUser1012,  manager001, /orgs/1/sites/site005\n\ng, managerUser1013, manager001, /orgs/1/sites/site001\ng, managerUser1013, manager001, /orgs/1/sites/site002\ng, managerUser1013, manager001, /orgs/1/sites/site003\ng, managerUser1013, manager001, /orgs/1/sites/site004\ng, managerUser1013, manager001, /orgs/1/sites/site005\n\ng, managerUser1014, manager001, /orgs/1/sites/site001\ng, managerUser1014, manager001, /orgs/1/sites/site002\ng, managerUser1014, manager001, /orgs/1/sites/site003\ng, managerUser1014, manager001, /orgs/1/sites/site004\ng, managerUser1014, manager001, /orgs/1/sites/site005\n\ng, managerUser1015, manager001, /orgs/1/sites/site001\ng, managerUser1015, manager001, /orgs/1/sites/site002\ng, managerUser1015, manager001, /orgs/1/sites/site003\ng, managerUser1015, manager001, /orgs/1/sites/site004\ng, managerUser1015, manager001, /orgs/1/sites/site005\n\ng, managerUser1016, manager001, /orgs/1/sites/site001\ng, managerUser1016, manager001, /orgs/1/sites/site002\ng, managerUser1016, manager001, /orgs/1/sites/site003\ng, managerUser1016, manager001, /orgs/1/sites/site004\ng, managerUser1016, manager001, /orgs/1/sites/site005\n\ng, managerUser1017, manager001, /orgs/1/sites/site001\ng, managerUser1017, manager001, /orgs/1/sites/site002\ng, managerUser1017, manager001, /orgs/1/sites/site003\ng, managerUser1017, manager001, /orgs/1/sites/site004\ng, managerUser1017, manager001, /orgs/1/sites/site005\n\ng, managerUser1018,  manager001, /orgs/1/sites/site001\ng, managerUser1018,  manager001, /orgs/1/sites/site002\ng, managerUser1018,  manager001, /orgs/1/sites/site003\ng, managerUser1018,  manager001, /orgs/1/sites/site004\ng, managerUser1018,  manager001, /orgs/1/sites/site005\n\ng, managerUser1019,  manager001, /orgs/1/sites/site001\ng, managerUser1019,  manager001, /orgs/1/sites/site002\ng, managerUser1019,  manager001, /orgs/1/sites/site003\ng, managerUser1019,  manager001, /orgs/1/sites/site004\ng, managerUser1019,  manager001, /orgs/1/sites/site005\n\ng, managerUser1020,  manager001, /orgs/1/sites/site001\ng, managerUser1020,  manager001, /orgs/1/sites/site002\ng, managerUser1020,  manager001, /orgs/1/sites/site003\ng, managerUser1020,  manager001, /orgs/1/sites/site004\ng, managerUser1020,  manager001, /orgs/1/sites/site005\n\ng, managerUser1021,  manager001, /orgs/1/sites/site001\ng, managerUser1021,  manager001, /orgs/1/sites/site002\ng, managerUser1021,  manager001, /orgs/1/sites/site003\ng, managerUser1021,  manager001, /orgs/1/sites/site004\ng, managerUser1021,  manager001, /orgs/1/sites/site005\n\ng, managerUser1022,  manager001, /orgs/1/sites/site001\ng, managerUser1022,  manager001, /orgs/1/sites/site002\ng, managerUser1022,  manager001, /orgs/1/sites/site003\ng, managerUser1022,  manager001, /orgs/1/sites/site004\ng, managerUser1022,  manager001, /orgs/1/sites/site005\n\ng, managerUser1023, manager001, /orgs/1/sites/site001\ng, managerUser1023, manager001, /orgs/1/sites/site002\ng, managerUser1023, manager001, /orgs/1/sites/site003\ng, managerUser1023, manager001, /orgs/1/sites/site004\ng, managerUser1023, manager001, /orgs/1/sites/site005\n\ng, managerUser1024, manager001, /orgs/1/sites/site001\ng, managerUser1024, manager001, /orgs/1/sites/site002\ng, managerUser1024, manager001, /orgs/1/sites/site003\ng, managerUser1024, manager001, /orgs/1/sites/site004\ng, managerUser1024, manager001, /orgs/1/sites/site005\n\ng, managerUser1025, manager001, /orgs/1/sites/site001\ng, managerUser1025, manager001, /orgs/1/sites/site002\ng, managerUser1025, manager001, /orgs/1/sites/site003\ng, managerUser1025, manager001, /orgs/1/sites/site004\ng, managerUser1025, manager001, /orgs/1/sites/site005\n\ng, managerUser1026, manager001, /orgs/1/sites/site001\ng, managerUser1026, manager001, /orgs/1/sites/site002\ng, managerUser1026, manager001, /orgs/1/sites/site003\ng, managerUser1026, manager001, /orgs/1/sites/site004\ng, managerUser1026, manager001, /orgs/1/sites/site005\n\ng, managerUser1027, manager001, /orgs/1/sites/site001\ng, managerUser1027, manager001, /orgs/1/sites/site002\ng, managerUser1027, manager001, /orgs/1/sites/site003\ng, managerUser1027, manager001, /orgs/1/sites/site004\ng, managerUser1027, manager001, /orgs/1/sites/site005\n\ng, managerUser1028,  manager001, /orgs/1/sites/site001\ng, managerUser1028,  manager001, /orgs/1/sites/site002\ng, managerUser1028,  manager001, /orgs/1/sites/site003\ng, managerUser1028,  manager001, /orgs/1/sites/site004\ng, managerUser1028,  manager001, /orgs/1/sites/site005\n\ng, managerUser1029,  manager001, /orgs/1/sites/site001\ng, managerUser1029,  manager001, /orgs/1/sites/site002\ng, managerUser1029,  manager001, /orgs/1/sites/site003\ng, managerUser1029,  manager001, /orgs/1/sites/site004\ng, managerUser1029,  manager001, /orgs/1/sites/site005\n\ng, managerUser1030,  manager001, /orgs/1/sites/site001\ng, managerUser1030,  manager001, /orgs/1/sites/site002\ng, managerUser1030,  manager001, /orgs/1/sites/site003\ng, managerUser1030,  manager001, /orgs/1/sites/site004\ng, managerUser1030,  manager001, /orgs/1/sites/site005\n\ng, managerUser1031,  manager001, /orgs/1/sites/site001\ng, managerUser1031,  manager001, /orgs/1/sites/site002\ng, managerUser1031,  manager001, /orgs/1/sites/site003\ng, managerUser1031,  manager001, /orgs/1/sites/site004\ng, managerUser1031,  manager001, /orgs/1/sites/site005\n\ng, managerUser1032,  manager001, /orgs/1/sites/site001\ng, managerUser1032,  manager001, /orgs/1/sites/site002\ng, managerUser1032,  manager001, /orgs/1/sites/site003\ng, managerUser1032,  manager001, /orgs/1/sites/site004\ng, managerUser1032,  manager001, /orgs/1/sites/site005\n\ng, managerUser1033, manager001, /orgs/1/sites/site001\ng, managerUser1033, manager001, /orgs/1/sites/site002\ng, managerUser1033, manager001, /orgs/1/sites/site003\ng, managerUser1033, manager001, /orgs/1/sites/site004\ng, managerUser1033, manager001, /orgs/1/sites/site005\n\ng, managerUser1034, manager001, /orgs/1/sites/site001\ng, managerUser1034, manager001, /orgs/1/sites/site002\ng, managerUser1034, manager001, /orgs/1/sites/site003\ng, managerUser1034, manager001, /orgs/1/sites/site004\ng, managerUser1034, manager001, /orgs/1/sites/site005\n\ng, managerUser1035, manager001, /orgs/1/sites/site001\ng, managerUser1035, manager001, /orgs/1/sites/site002\ng, managerUser1035, manager001, /orgs/1/sites/site003\ng, managerUser1035, manager001, /orgs/1/sites/site004\ng, managerUser1035, manager001, /orgs/1/sites/site005\n\ng, managerUser1036, manager001, /orgs/1/sites/site001\ng, managerUser1036, manager001, /orgs/1/sites/site002\ng, managerUser1036, manager001, /orgs/1/sites/site003\ng, managerUser1036, manager001, /orgs/1/sites/site004\ng, managerUser1036, manager001, /orgs/1/sites/site005\n\ng, managerUser1037, manager001, /orgs/1/sites/site001\ng, managerUser1037, manager001, /orgs/1/sites/site002\ng, managerUser1037, manager001, /orgs/1/sites/site003\ng, managerUser1037, manager001, /orgs/1/sites/site004\ng, managerUser1037, manager001, /orgs/1/sites/site005\n\ng, managerUser1038,  manager001, /orgs/1/sites/site001\ng, managerUser1038,  manager001, /orgs/1/sites/site002\ng, managerUser1038,  manager001, /orgs/1/sites/site003\ng, managerUser1038,  manager001, /orgs/1/sites/site004\ng, managerUser1038,  manager001, /orgs/1/sites/site005\n\ng, managerUser1039,  manager001, /orgs/1/sites/site001\ng, managerUser1039,  manager001, /orgs/1/sites/site002\ng, managerUser1039,  manager001, /orgs/1/sites/site003\ng, managerUser1039,  manager001, /orgs/1/sites/site004\ng, managerUser1039,  manager001, /orgs/1/sites/site005\n\ng, managerUser1040,  manager001, /orgs/1/sites/site001\ng, managerUser1040,  manager001, /orgs/1/sites/site002\ng, managerUser1040,  manager001, /orgs/1/sites/site003\ng, managerUser1040,  manager001, /orgs/1/sites/site004\ng, managerUser1040,  manager001, /orgs/1/sites/site005\n\ng, managerUser1041,  manager001, /orgs/1/sites/site001\ng, managerUser1041,  manager001, /orgs/1/sites/site002\ng, managerUser1041,  manager001, /orgs/1/sites/site003\ng, managerUser1041,  manager001, /orgs/1/sites/site004\ng, managerUser1041,  manager001, /orgs/1/sites/site005\n\ng, managerUser1042,  manager001, /orgs/1/sites/site001\ng, managerUser1042,  manager001, /orgs/1/sites/site002\ng, managerUser1042,  manager001, /orgs/1/sites/site003\ng, managerUser1042,  manager001, /orgs/1/sites/site004\ng, managerUser1042,  manager001, /orgs/1/sites/site005\n\ng, managerUser1043, manager001, /orgs/1/sites/site001\ng, managerUser1043, manager001, /orgs/1/sites/site002\ng, managerUser1043, manager001, /orgs/1/sites/site003\ng, managerUser1043, manager001, /orgs/1/sites/site004\ng, managerUser1043, manager001, /orgs/1/sites/site005\n\ng, managerUser1044, manager001, /orgs/1/sites/site001\ng, managerUser1044, manager001, /orgs/1/sites/site002\ng, managerUser1044, manager001, /orgs/1/sites/site003\ng, managerUser1044, manager001, /orgs/1/sites/site004\ng, managerUser1044, manager001, /orgs/1/sites/site005\n\ng, managerUser1045, manager001, /orgs/1/sites/site001\ng, managerUser1045, manager001, /orgs/1/sites/site002\ng, managerUser1045, manager001, /orgs/1/sites/site003\ng, managerUser1045, manager001, /orgs/1/sites/site004\ng, managerUser1045, manager001, /orgs/1/sites/site005\n\ng, managerUser1046, manager001, /orgs/1/sites/site001\ng, managerUser1046, manager001, /orgs/1/sites/site002\ng, managerUser1046, manager001, /orgs/1/sites/site003\ng, managerUser1046, manager001, /orgs/1/sites/site004\ng, managerUser1046, manager001, /orgs/1/sites/site005\n\ng, managerUser1047, manager001, /orgs/1/sites/site001\ng, managerUser1047, manager001, /orgs/1/sites/site002\ng, managerUser1047, manager001, /orgs/1/sites/site003\ng, managerUser1047, manager001, /orgs/1/sites/site004\ng, managerUser1047, manager001, /orgs/1/sites/site005\n\ng, managerUser1048,  manager001, /orgs/1/sites/site001\ng, managerUser1048,  manager001, /orgs/1/sites/site002\ng, managerUser1048,  manager001, /orgs/1/sites/site003\ng, managerUser1048,  manager001, /orgs/1/sites/site004\ng, managerUser1048,  manager001, /orgs/1/sites/site005\n\ng, managerUser1049,  manager001, /orgs/1/sites/site001\ng, managerUser1049,  manager001, /orgs/1/sites/site002\ng, managerUser1049,  manager001, /orgs/1/sites/site003\ng, managerUser1049,  manager001, /orgs/1/sites/site004\ng, managerUser1049,  manager001, /orgs/1/sites/site005\n\ng, managerUser1050,  manager001, /orgs/1/sites/site001\ng, managerUser1050,  manager001, /orgs/1/sites/site002\ng, managerUser1050,  manager001, /orgs/1/sites/site003\ng, managerUser1050,  manager001, /orgs/1/sites/site004\ng, managerUser1050,  manager001, /orgs/1/sites/site005\n\n# Group - manager001, / org1\ng, managerUser2001, manager001, /orgs/1/sites/site001\ng, managerUser2001, manager001, /orgs/1/sites/site002\ng, managerUser2001, manager001, /orgs/1/sites/site003\ng, managerUser2001, manager001, /orgs/1/sites/site004\ng, managerUser2001, manager001, /orgs/1/sites/site005\n\ng, managerUser2001, manager001, /orgs/1/sites/site001\ng, managerUser2001, manager001, /orgs/1/sites/site002\ng, managerUser2001, manager001, /orgs/1/sites/site003\ng, managerUser2001, manager001, /orgs/1/sites/site004\ng, managerUser2001, manager001, /orgs/1/sites/site005\n\ng, managerUser2003, manager001, /orgs/1/sites/site001\ng, managerUser2003, manager001, /orgs/1/sites/site002\ng, managerUser2003, manager001, /orgs/1/sites/site003\ng, managerUser2003, manager001, /orgs/1/sites/site004\ng, managerUser2003, manager001, /orgs/1/sites/site005\n\ng, managerUser2004, manager001, /orgs/1/sites/site001\ng, managerUser2004, manager001, /orgs/1/sites/site002\ng, managerUser2004, manager001, /orgs/1/sites/site003\ng, managerUser2004, manager001, /orgs/1/sites/site004\ng, managerUser2004, manager001, /orgs/1/sites/site005\n\ng, managerUser2005, manager001, /orgs/1/sites/site001\ng, managerUser2005, manager001, /orgs/1/sites/site002\ng, managerUser2005, manager001, /orgs/1/sites/site003\ng, managerUser2005, manager001, /orgs/1/sites/site004\ng, managerUser2005, manager001, /orgs/1/sites/site005\n\ng, managerUser2006, manager001, /orgs/1/sites/site001\ng, managerUser2006, manager001, /orgs/1/sites/site002\ng, managerUser2006, manager001, /orgs/1/sites/site003\ng, managerUser2006, manager001, /orgs/1/sites/site004\ng, managerUser2006, manager001, /orgs/1/sites/site005\n\ng, managerUser2007, manager001, /orgs/1/sites/site001\ng, managerUser2007, manager001, /orgs/1/sites/site002\ng, managerUser2007, manager001, /orgs/1/sites/site003\ng, managerUser2007, manager001, /orgs/1/sites/site004\ng, managerUser2007, manager001, /orgs/1/sites/site005\n\ng, managerUser2008,  manager001, /orgs/1/sites/site001\ng, managerUser2008,  manager001, /orgs/1/sites/site002\ng, managerUser2008,  manager001, /orgs/1/sites/site003\ng, managerUser2008,  manager001, /orgs/1/sites/site004\ng, managerUser2008,  manager001, /orgs/1/sites/site005\n\ng, managerUser2009,  manager001, /orgs/1/sites/site001\ng, managerUser2009,  manager001, /orgs/1/sites/site002\ng, managerUser2009,  manager001, /orgs/1/sites/site003\ng, managerUser2009,  manager001, /orgs/1/sites/site004\ng, managerUser2009,  manager001, /orgs/1/sites/site005\n\ng, managerUser2010,  manager001, /orgs/1/sites/site001\ng, managerUser2010,  manager001, /orgs/1/sites/site002\ng, managerUser2010,  manager001, /orgs/1/sites/site003\ng, managerUser2010,  manager001, /orgs/1/sites/site004\ng, managerUser2010,  manager001, /orgs/1/sites/site005\n\ng, managerUser2011,  manager001, /orgs/1/sites/site001\ng, managerUser2011,  manager001, /orgs/1/sites/site002\ng, managerUser2011,  manager001, /orgs/1/sites/site003\ng, managerUser2011,  manager001, /orgs/1/sites/site004\ng, managerUser2011,  manager001, /orgs/1/sites/site005\n\ng, managerUser2012,  manager001, /orgs/1/sites/site001\ng, managerUser2012,  manager001, /orgs/1/sites/site002\ng, managerUser2012,  manager001, /orgs/1/sites/site003\ng, managerUser2012,  manager001, /orgs/1/sites/site004\ng, managerUser2012,  manager001, /orgs/1/sites/site005\n\ng, managerUser2013, manager001, /orgs/1/sites/site001\ng, managerUser2013, manager001, /orgs/1/sites/site002\ng, managerUser2013, manager001, /orgs/1/sites/site003\ng, managerUser2013, manager001, /orgs/1/sites/site004\ng, managerUser2013, manager001, /orgs/1/sites/site005\n\ng, managerUser2014, manager001, /orgs/1/sites/site001\ng, managerUser2014, manager001, /orgs/1/sites/site002\ng, managerUser2014, manager001, /orgs/1/sites/site003\ng, managerUser2014, manager001, /orgs/1/sites/site004\ng, managerUser2014, manager001, /orgs/1/sites/site005\n\ng, managerUser2015, manager001, /orgs/1/sites/site001\ng, managerUser2015, manager001, /orgs/1/sites/site002\ng, managerUser2015, manager001, /orgs/1/sites/site003\ng, managerUser2015, manager001, /orgs/1/sites/site004\ng, managerUser2015, manager001, /orgs/1/sites/site005\n\ng, managerUser2016, manager001, /orgs/1/sites/site001\ng, managerUser2016, manager001, /orgs/1/sites/site002\ng, managerUser2016, manager001, /orgs/1/sites/site003\ng, managerUser2016, manager001, /orgs/1/sites/site004\ng, managerUser2016, manager001, /orgs/1/sites/site005\n\ng, managerUser2017, manager001, /orgs/1/sites/site001\ng, managerUser2017, manager001, /orgs/1/sites/site002\ng, managerUser2017, manager001, /orgs/1/sites/site003\ng, managerUser2017, manager001, /orgs/1/sites/site004\ng, managerUser2017, manager001, /orgs/1/sites/site005\n\ng, managerUser2018,  manager001, /orgs/1/sites/site001\ng, managerUser2018,  manager001, /orgs/1/sites/site002\ng, managerUser2018,  manager001, /orgs/1/sites/site003\ng, managerUser2018,  manager001, /orgs/1/sites/site004\ng, managerUser2018,  manager001, /orgs/1/sites/site005\n\ng, managerUser2019,  manager001, /orgs/1/sites/site001\ng, managerUser2019,  manager001, /orgs/1/sites/site002\ng, managerUser2019,  manager001, /orgs/1/sites/site003\ng, managerUser2019,  manager001, /orgs/1/sites/site004\ng, managerUser2019,  manager001, /orgs/1/sites/site005\n\ng, managerUser2020,  manager001, /orgs/1/sites/site001\ng, managerUser2020,  manager001, /orgs/1/sites/site002\ng, managerUser2020,  manager001, /orgs/1/sites/site003\ng, managerUser2020,  manager001, /orgs/1/sites/site004\ng, managerUser2020,  manager001, /orgs/1/sites/site005\n\ng, managerUser2021,  manager001, /orgs/1/sites/site001\ng, managerUser2021,  manager001, /orgs/1/sites/site002\ng, managerUser2021,  manager001, /orgs/1/sites/site003\ng, managerUser2021,  manager001, /orgs/1/sites/site004\ng, managerUser2021,  manager001, /orgs/1/sites/site005\n\ng, managerUser2022,  manager001, /orgs/1/sites/site001\ng, managerUser2022,  manager001, /orgs/1/sites/site002\ng, managerUser2022,  manager001, /orgs/1/sites/site003\ng, managerUser2022,  manager001, /orgs/1/sites/site004\ng, managerUser2022,  manager001, /orgs/1/sites/site005\n\ng, managerUser2023, manager001, /orgs/1/sites/site001\ng, managerUser2023, manager001, /orgs/1/sites/site002\ng, managerUser2023, manager001, /orgs/1/sites/site003\ng, managerUser2023, manager001, /orgs/1/sites/site004\ng, managerUser2023, manager001, /orgs/1/sites/site005\n\ng, managerUser2024, manager001, /orgs/1/sites/site001\ng, managerUser2024, manager001, /orgs/1/sites/site002\ng, managerUser2024, manager001, /orgs/1/sites/site003\ng, managerUser2024, manager001, /orgs/1/sites/site004\ng, managerUser2024, manager001, /orgs/1/sites/site005\n\ng, managerUser2025, manager001, /orgs/1/sites/site001\ng, managerUser2025, manager001, /orgs/1/sites/site002\ng, managerUser2025, manager001, /orgs/1/sites/site003\ng, managerUser2025, manager001, /orgs/1/sites/site004\ng, managerUser2025, manager001, /orgs/1/sites/site005\n\ng, managerUser2026, manager001, /orgs/1/sites/site001\ng, managerUser2026, manager001, /orgs/1/sites/site002\ng, managerUser2026, manager001, /orgs/1/sites/site003\ng, managerUser2026, manager001, /orgs/1/sites/site004\ng, managerUser2026, manager001, /orgs/1/sites/site005\n\ng, managerUser2027, manager001, /orgs/1/sites/site001\ng, managerUser2027, manager001, /orgs/1/sites/site002\ng, managerUser2027, manager001, /orgs/1/sites/site003\ng, managerUser2027, manager001, /orgs/1/sites/site004\ng, managerUser2027, manager001, /orgs/1/sites/site005\n\ng, managerUser2028,  manager001, /orgs/1/sites/site001\ng, managerUser2028,  manager001, /orgs/1/sites/site002\ng, managerUser2028,  manager001, /orgs/1/sites/site003\ng, managerUser2028,  manager001, /orgs/1/sites/site004\ng, managerUser2028,  manager001, /orgs/1/sites/site005\n\ng, managerUser2029,  manager001, /orgs/1/sites/site001\ng, managerUser2029,  manager001, /orgs/1/sites/site002\ng, managerUser2029,  manager001, /orgs/1/sites/site003\ng, managerUser2029,  manager001, /orgs/1/sites/site004\ng, managerUser2029,  manager001, /orgs/1/sites/site005\n\ng, managerUser2030,  manager001, /orgs/1/sites/site001\ng, managerUser2030,  manager001, /orgs/1/sites/site002\ng, managerUser2030,  manager001, /orgs/1/sites/site003\ng, managerUser2030,  manager001, /orgs/1/sites/site004\ng, managerUser2030,  manager001, /orgs/1/sites/site005\n\ng, managerUser2031,  manager001, /orgs/1/sites/site001\ng, managerUser2031,  manager001, /orgs/1/sites/site002\ng, managerUser2031,  manager001, /orgs/1/sites/site003\ng, managerUser2031,  manager001, /orgs/1/sites/site004\ng, managerUser2031,  manager001, /orgs/1/sites/site005\n\ng, managerUser2032,  manager001, /orgs/1/sites/site001\ng, managerUser2032,  manager001, /orgs/1/sites/site002\ng, managerUser2032,  manager001, /orgs/1/sites/site003\ng, managerUser2032,  manager001, /orgs/1/sites/site004\ng, managerUser2032,  manager001, /orgs/1/sites/site005\n\ng, managerUser2033, manager001, /orgs/1/sites/site001\ng, managerUser2033, manager001, /orgs/1/sites/site002\ng, managerUser2033, manager001, /orgs/1/sites/site003\ng, managerUser2033, manager001, /orgs/1/sites/site004\ng, managerUser2033, manager001, /orgs/1/sites/site005\n\ng, managerUser2034, manager001, /orgs/1/sites/site001\ng, managerUser2034, manager001, /orgs/1/sites/site002\ng, managerUser2034, manager001, /orgs/1/sites/site003\ng, managerUser2034, manager001, /orgs/1/sites/site004\ng, managerUser2034, manager001, /orgs/1/sites/site005\n\ng, managerUser2035, manager001, /orgs/1/sites/site001\ng, managerUser2035, manager001, /orgs/1/sites/site002\ng, managerUser2035, manager001, /orgs/1/sites/site003\ng, managerUser2035, manager001, /orgs/1/sites/site004\ng, managerUser2035, manager001, /orgs/1/sites/site005\n\ng, managerUser2036, manager001, /orgs/1/sites/site001\ng, managerUser2036, manager001, /orgs/1/sites/site002\ng, managerUser2036, manager001, /orgs/1/sites/site003\ng, managerUser2036, manager001, /orgs/1/sites/site004\ng, managerUser2036, manager001, /orgs/1/sites/site005\n\ng, managerUser2037, manager001, /orgs/1/sites/site001\ng, managerUser2037, manager001, /orgs/1/sites/site002\ng, managerUser2037, manager001, /orgs/1/sites/site003\ng, managerUser2037, manager001, /orgs/1/sites/site004\ng, managerUser2037, manager001, /orgs/1/sites/site005\n\ng, managerUser2038,  manager001, /orgs/1/sites/site001\ng, managerUser2038,  manager001, /orgs/1/sites/site002\ng, managerUser2038,  manager001, /orgs/1/sites/site003\ng, managerUser2038,  manager001, /orgs/1/sites/site004\ng, managerUser2038,  manager001, /orgs/1/sites/site005\n\ng, managerUser2039,  manager001, /orgs/1/sites/site001\ng, managerUser2039,  manager001, /orgs/1/sites/site002\ng, managerUser2039,  manager001, /orgs/1/sites/site003\ng, managerUser2039,  manager001, /orgs/1/sites/site004\ng, managerUser2039,  manager001, /orgs/1/sites/site005\n\ng, managerUser2040,  manager001, /orgs/1/sites/site001\ng, managerUser2040,  manager001, /orgs/1/sites/site002\ng, managerUser2040,  manager001, /orgs/1/sites/site003\ng, managerUser2040,  manager001, /orgs/1/sites/site004\ng, managerUser2040,  manager001, /orgs/1/sites/site005\n\ng, managerUser2041,  manager001, /orgs/1/sites/site001\ng, managerUser2041,  manager001, /orgs/1/sites/site002\ng, managerUser2041,  manager001, /orgs/1/sites/site003\ng, managerUser2041,  manager001, /orgs/1/sites/site004\ng, managerUser2041,  manager001, /orgs/1/sites/site005\n\ng, managerUser2042,  manager001, /orgs/1/sites/site001\ng, managerUser2042,  manager001, /orgs/1/sites/site002\ng, managerUser2042,  manager001, /orgs/1/sites/site003\ng, managerUser2042,  manager001, /orgs/1/sites/site004\ng, managerUser2042,  manager001, /orgs/1/sites/site005\n\ng, managerUser2043, manager001, /orgs/1/sites/site001\ng, managerUser2043, manager001, /orgs/1/sites/site002\ng, managerUser2043, manager001, /orgs/1/sites/site003\ng, managerUser2043, manager001, /orgs/1/sites/site004\ng, managerUser2043, manager001, /orgs/1/sites/site005\n\ng, managerUser2044, manager001, /orgs/1/sites/site001\ng, managerUser2044, manager001, /orgs/1/sites/site002\ng, managerUser2044, manager001, /orgs/1/sites/site003\ng, managerUser2044, manager001, /orgs/1/sites/site004\ng, managerUser2044, manager001, /orgs/1/sites/site005\n\ng, managerUser2045, manager001, /orgs/1/sites/site001\ng, managerUser2045, manager001, /orgs/1/sites/site002\ng, managerUser2045, manager001, /orgs/1/sites/site003\ng, managerUser2045, manager001, /orgs/1/sites/site004\ng, managerUser2045, manager001, /orgs/1/sites/site005\n\ng, managerUser2046, manager001, /orgs/1/sites/site001\ng, managerUser2046, manager001, /orgs/1/sites/site002\ng, managerUser2046, manager001, /orgs/1/sites/site003\ng, managerUser2046, manager001, /orgs/1/sites/site004\ng, managerUser2046, manager001, /orgs/1/sites/site005\n\ng, managerUser2047, manager001, /orgs/1/sites/site001\ng, managerUser2047, manager001, /orgs/1/sites/site002\ng, managerUser2047, manager001, /orgs/1/sites/site003\ng, managerUser2047, manager001, /orgs/1/sites/site004\ng, managerUser2047, manager001, /orgs/1/sites/site005\n\ng, managerUser2048,  manager001, /orgs/1/sites/site001\ng, managerUser2048,  manager001, /orgs/1/sites/site002\ng, managerUser2048,  manager001, /orgs/1/sites/site003\ng, managerUser2048,  manager001, /orgs/1/sites/site004\ng, managerUser2048,  manager001, /orgs/1/sites/site005\n\ng, managerUser2049,  manager001, /orgs/1/sites/site001\ng, managerUser2049,  manager001, /orgs/1/sites/site002\ng, managerUser2049,  manager001, /orgs/1/sites/site003\ng, managerUser2049,  manager001, /orgs/1/sites/site004\ng, managerUser2049,  manager001, /orgs/1/sites/site005\n\ng, managerUser2050,  manager001, /orgs/1/sites/site001\ng, managerUser2050,  manager001, /orgs/1/sites/site002\ng, managerUser2050,  manager001, /orgs/1/sites/site003\ng, managerUser2050,  manager001, /orgs/1/sites/site004\ng, managerUser2050,  manager001, /orgs/1/sites/site005\n\n# Group - customer001, / org1\ng, customerUser1001, customer001, /orgs/1/sites/site001\ng, customerUser1001, customer001, /orgs/1/sites/site002\ng, customerUser1001, customer001, /orgs/1/sites/site003\ng, customerUser1001, customer001, /orgs/1/sites/site004\ng, customerUser1001, customer001, /orgs/1/sites/site005\n\ng, customerUser1001, customer001, /orgs/1/sites/site001\ng, customerUser1001, customer001, /orgs/1/sites/site002\ng, customerUser1001, customer001, /orgs/1/sites/site003\ng, customerUser1001, customer001, /orgs/1/sites/site004\ng, customerUser1001, customer001, /orgs/1/sites/site005\n\ng, customerUser1003, customer001, /orgs/1/sites/site001\ng, customerUser1003, customer001, /orgs/1/sites/site002\ng, customerUser1003, customer001, /orgs/1/sites/site003\ng, customerUser1003, customer001, /orgs/1/sites/site004\ng, customerUser1003, customer001, /orgs/1/sites/site005\n\ng, customerUser1004, customer001, /orgs/1/sites/site001\ng, customerUser1004, customer001, /orgs/1/sites/site002\ng, customerUser1004, customer001, /orgs/1/sites/site003\ng, customerUser1004, customer001, /orgs/1/sites/site004\ng, customerUser1004, customer001, /orgs/1/sites/site005\n\ng, customerUser1005, customer001, /orgs/1/sites/site001\ng, customerUser1005, customer001, /orgs/1/sites/site002\ng, customerUser1005, customer001, /orgs/1/sites/site003\ng, customerUser1005, customer001, /orgs/1/sites/site004\ng, customerUser1005, customer001, /orgs/1/sites/site005\n\ng, customerUser1006, customer001, /orgs/1/sites/site001\ng, customerUser1006, customer001, /orgs/1/sites/site002\ng, customerUser1006, customer001, /orgs/1/sites/site003\ng, customerUser1006, customer001, /orgs/1/sites/site004\ng, customerUser1006, customer001, /orgs/1/sites/site005\n\ng, customerUser1007, customer001, /orgs/1/sites/site001\ng, customerUser1007, customer001, /orgs/1/sites/site002\ng, customerUser1007, customer001, /orgs/1/sites/site003\ng, customerUser1007, customer001, /orgs/1/sites/site004\ng, customerUser1007, customer001, /orgs/1/sites/site005\n\ng, customerUser1008,  customer001, /orgs/1/sites/site001\ng, customerUser1008,  customer001, /orgs/1/sites/site002\ng, customerUser1008,  customer001, /orgs/1/sites/site003\ng, customerUser1008,  customer001, /orgs/1/sites/site004\ng, customerUser1008,  customer001, /orgs/1/sites/site005\n\ng, customerUser1009,  customer001, /orgs/1/sites/site001\ng, customerUser1009,  customer001, /orgs/1/sites/site002\ng, customerUser1009,  customer001, /orgs/1/sites/site003\ng, customerUser1009,  customer001, /orgs/1/sites/site004\ng, customerUser1009,  customer001, /orgs/1/sites/site005\n\ng, customerUser1010,  customer001, /orgs/1/sites/site001\ng, customerUser1010,  customer001, /orgs/1/sites/site002\ng, customerUser1010,  customer001, /orgs/1/sites/site003\ng, customerUser1010,  customer001, /orgs/1/sites/site004\ng, customerUser1010,  customer001, /orgs/1/sites/site005\n\ng, customerUser1011,  customer001, /orgs/1/sites/site001\ng, customerUser1011,  customer001, /orgs/1/sites/site002\ng, customerUser1011,  customer001, /orgs/1/sites/site003\ng, customerUser1011,  customer001, /orgs/1/sites/site004\ng, customerUser1011,  customer001, /orgs/1/sites/site005\n\ng, customerUser1012,  customer001, /orgs/1/sites/site001\ng, customerUser1012,  customer001, /orgs/1/sites/site002\ng, customerUser1012,  customer001, /orgs/1/sites/site003\ng, customerUser1012,  customer001, /orgs/1/sites/site004\ng, customerUser1012,  customer001, /orgs/1/sites/site005\n\ng, customerUser1013, customer001, /orgs/1/sites/site001\ng, customerUser1013, customer001, /orgs/1/sites/site002\ng, customerUser1013, customer001, /orgs/1/sites/site003\ng, customerUser1013, customer001, /orgs/1/sites/site004\ng, customerUser1013, customer001, /orgs/1/sites/site005\n\ng, customerUser1014, customer001, /orgs/1/sites/site001\ng, customerUser1014, customer001, /orgs/1/sites/site002\ng, customerUser1014, customer001, /orgs/1/sites/site003\ng, customerUser1014, customer001, /orgs/1/sites/site004\ng, customerUser1014, customer001, /orgs/1/sites/site005\n\ng, customerUser1015, customer001, /orgs/1/sites/site001\ng, customerUser1015, customer001, /orgs/1/sites/site002\ng, customerUser1015, customer001, /orgs/1/sites/site003\ng, customerUser1015, customer001, /orgs/1/sites/site004\ng, customerUser1015, customer001, /orgs/1/sites/site005\n\ng, customerUser1016, customer001, /orgs/1/sites/site001\ng, customerUser1016, customer001, /orgs/1/sites/site002\ng, customerUser1016, customer001, /orgs/1/sites/site003\ng, customerUser1016, customer001, /orgs/1/sites/site004\ng, customerUser1016, customer001, /orgs/1/sites/site005\n\ng, customerUser1017, customer001, /orgs/1/sites/site001\ng, customerUser1017, customer001, /orgs/1/sites/site002\ng, customerUser1017, customer001, /orgs/1/sites/site003\ng, customerUser1017, customer001, /orgs/1/sites/site004\ng, customerUser1017, customer001, /orgs/1/sites/site005\n\ng, customerUser1018,  customer001, /orgs/1/sites/site001\ng, customerUser1018,  customer001, /orgs/1/sites/site002\ng, customerUser1018,  customer001, /orgs/1/sites/site003\ng, customerUser1018,  customer001, /orgs/1/sites/site004\ng, customerUser1018,  customer001, /orgs/1/sites/site005\n\ng, customerUser1019,  customer001, /orgs/1/sites/site001\ng, customerUser1019,  customer001, /orgs/1/sites/site002\ng, customerUser1019,  customer001, /orgs/1/sites/site003\ng, customerUser1019,  customer001, /orgs/1/sites/site004\ng, customerUser1019,  customer001, /orgs/1/sites/site005\n\ng, customerUser1020,  customer001, /orgs/1/sites/site001\ng, customerUser1020,  customer001, /orgs/1/sites/site002\ng, customerUser1020,  customer001, /orgs/1/sites/site003\ng, customerUser1020,  customer001, /orgs/1/sites/site004\ng, customerUser1020,  customer001, /orgs/1/sites/site005\n\ng, customerUser1021,  customer001, /orgs/1/sites/site001\ng, customerUser1021,  customer001, /orgs/1/sites/site002\ng, customerUser1021,  customer001, /orgs/1/sites/site003\ng, customerUser1021,  customer001, /orgs/1/sites/site004\ng, customerUser1021,  customer001, /orgs/1/sites/site005\n\ng, customerUser1022,  customer001, /orgs/1/sites/site001\ng, customerUser1022,  customer001, /orgs/1/sites/site002\ng, customerUser1022,  customer001, /orgs/1/sites/site003\ng, customerUser1022,  customer001, /orgs/1/sites/site004\ng, customerUser1022,  customer001, /orgs/1/sites/site005\n\ng, customerUser1023, customer001, /orgs/1/sites/site001\ng, customerUser1023, customer001, /orgs/1/sites/site002\ng, customerUser1023, customer001, /orgs/1/sites/site003\ng, customerUser1023, customer001, /orgs/1/sites/site004\ng, customerUser1023, customer001, /orgs/1/sites/site005\n\ng, customerUser1024, customer001, /orgs/1/sites/site001\ng, customerUser1024, customer001, /orgs/1/sites/site002\ng, customerUser1024, customer001, /orgs/1/sites/site003\ng, customerUser1024, customer001, /orgs/1/sites/site004\ng, customerUser1024, customer001, /orgs/1/sites/site005\n\ng, customerUser1025, customer001, /orgs/1/sites/site001\ng, customerUser1025, customer001, /orgs/1/sites/site002\ng, customerUser1025, customer001, /orgs/1/sites/site003\ng, customerUser1025, customer001, /orgs/1/sites/site004\ng, customerUser1025, customer001, /orgs/1/sites/site005\n\ng, customerUser1026, customer001, /orgs/1/sites/site001\ng, customerUser1026, customer001, /orgs/1/sites/site002\ng, customerUser1026, customer001, /orgs/1/sites/site003\ng, customerUser1026, customer001, /orgs/1/sites/site004\ng, customerUser1026, customer001, /orgs/1/sites/site005\n\ng, customerUser1027, customer001, /orgs/1/sites/site001\ng, customerUser1027, customer001, /orgs/1/sites/site002\ng, customerUser1027, customer001, /orgs/1/sites/site003\ng, customerUser1027, customer001, /orgs/1/sites/site004\ng, customerUser1027, customer001, /orgs/1/sites/site005\n\ng, customerUser1028,  customer001, /orgs/1/sites/site001\ng, customerUser1028,  customer001, /orgs/1/sites/site002\ng, customerUser1028,  customer001, /orgs/1/sites/site003\ng, customerUser1028,  customer001, /orgs/1/sites/site004\ng, customerUser1028,  customer001, /orgs/1/sites/site005\n\ng, customerUser1029,  customer001, /orgs/1/sites/site001\ng, customerUser1029,  customer001, /orgs/1/sites/site002\ng, customerUser1029,  customer001, /orgs/1/sites/site003\ng, customerUser1029,  customer001, /orgs/1/sites/site004\ng, customerUser1029,  customer001, /orgs/1/sites/site005\n\ng, customerUser1030,  customer001, /orgs/1/sites/site001\ng, customerUser1030,  customer001, /orgs/1/sites/site002\ng, customerUser1030,  customer001, /orgs/1/sites/site003\ng, customerUser1030,  customer001, /orgs/1/sites/site004\ng, customerUser1030,  customer001, /orgs/1/sites/site005\n\ng, customerUser1031,  customer001, /orgs/1/sites/site001\ng, customerUser1031,  customer001, /orgs/1/sites/site002\ng, customerUser1031,  customer001, /orgs/1/sites/site003\ng, customerUser1031,  customer001, /orgs/1/sites/site004\ng, customerUser1031,  customer001, /orgs/1/sites/site005\n\ng, customerUser1032,  customer001, /orgs/1/sites/site001\ng, customerUser1032,  customer001, /orgs/1/sites/site002\ng, customerUser1032,  customer001, /orgs/1/sites/site003\ng, customerUser1032,  customer001, /orgs/1/sites/site004\ng, customerUser1032,  customer001, /orgs/1/sites/site005\n\ng, customerUser1033, customer001, /orgs/1/sites/site001\ng, customerUser1033, customer001, /orgs/1/sites/site002\ng, customerUser1033, customer001, /orgs/1/sites/site003\ng, customerUser1033, customer001, /orgs/1/sites/site004\ng, customerUser1033, customer001, /orgs/1/sites/site005\n\ng, customerUser1034, customer001, /orgs/1/sites/site001\ng, customerUser1034, customer001, /orgs/1/sites/site002\ng, customerUser1034, customer001, /orgs/1/sites/site003\ng, customerUser1034, customer001, /orgs/1/sites/site004\ng, customerUser1034, customer001, /orgs/1/sites/site005\n\ng, customerUser1035, customer001, /orgs/1/sites/site001\ng, customerUser1035, customer001, /orgs/1/sites/site002\ng, customerUser1035, customer001, /orgs/1/sites/site003\ng, customerUser1035, customer001, /orgs/1/sites/site004\ng, customerUser1035, customer001, /orgs/1/sites/site005\n\ng, customerUser1036, customer001, /orgs/1/sites/site001\ng, customerUser1036, customer001, /orgs/1/sites/site002\ng, customerUser1036, customer001, /orgs/1/sites/site003\ng, customerUser1036, customer001, /orgs/1/sites/site004\ng, customerUser1036, customer001, /orgs/1/sites/site005\n\ng, customerUser1037, customer001, /orgs/1/sites/site001\ng, customerUser1037, customer001, /orgs/1/sites/site002\ng, customerUser1037, customer001, /orgs/1/sites/site003\ng, customerUser1037, customer001, /orgs/1/sites/site004\ng, customerUser1037, customer001, /orgs/1/sites/site005\n\ng, customerUser1038,  customer001, /orgs/1/sites/site001\ng, customerUser1038,  customer001, /orgs/1/sites/site002\ng, customerUser1038,  customer001, /orgs/1/sites/site003\ng, customerUser1038,  customer001, /orgs/1/sites/site004\ng, customerUser1038,  customer001, /orgs/1/sites/site005\n\ng, customerUser1039,  customer001, /orgs/1/sites/site001\ng, customerUser1039,  customer001, /orgs/1/sites/site002\ng, customerUser1039,  customer001, /orgs/1/sites/site003\ng, customerUser1039,  customer001, /orgs/1/sites/site004\ng, customerUser1039,  customer001, /orgs/1/sites/site005\n\ng, customerUser1040,  customer001, /orgs/1/sites/site001\ng, customerUser1040,  customer001, /orgs/1/sites/site002\ng, customerUser1040,  customer001, /orgs/1/sites/site003\ng, customerUser1040,  customer001, /orgs/1/sites/site004\ng, customerUser1040,  customer001, /orgs/1/sites/site005\n\ng, customerUser1041,  customer001, /orgs/1/sites/site001\ng, customerUser1041,  customer001, /orgs/1/sites/site002\ng, customerUser1041,  customer001, /orgs/1/sites/site003\ng, customerUser1041,  customer001, /orgs/1/sites/site004\ng, customerUser1041,  customer001, /orgs/1/sites/site005\n\ng, customerUser1042,  customer001, /orgs/1/sites/site001\ng, customerUser1042,  customer001, /orgs/1/sites/site002\ng, customerUser1042,  customer001, /orgs/1/sites/site003\ng, customerUser1042,  customer001, /orgs/1/sites/site004\ng, customerUser1042,  customer001, /orgs/1/sites/site005\n\ng, customerUser1043, customer001, /orgs/1/sites/site001\ng, customerUser1043, customer001, /orgs/1/sites/site002\ng, customerUser1043, customer001, /orgs/1/sites/site003\ng, customerUser1043, customer001, /orgs/1/sites/site004\ng, customerUser1043, customer001, /orgs/1/sites/site005\n\ng, customerUser1044, customer001, /orgs/1/sites/site001\ng, customerUser1044, customer001, /orgs/1/sites/site002\ng, customerUser1044, customer001, /orgs/1/sites/site003\ng, customerUser1044, customer001, /orgs/1/sites/site004\ng, customerUser1044, customer001, /orgs/1/sites/site005\n\ng, customerUser1045, customer001, /orgs/1/sites/site001\ng, customerUser1045, customer001, /orgs/1/sites/site002\ng, customerUser1045, customer001, /orgs/1/sites/site003\ng, customerUser1045, customer001, /orgs/1/sites/site004\ng, customerUser1045, customer001, /orgs/1/sites/site005\n\ng, customerUser1046, customer001, /orgs/1/sites/site001\ng, customerUser1046, customer001, /orgs/1/sites/site002\ng, customerUser1046, customer001, /orgs/1/sites/site003\ng, customerUser1046, customer001, /orgs/1/sites/site004\ng, customerUser1046, customer001, /orgs/1/sites/site005\n\ng, customerUser1047, customer001, /orgs/1/sites/site001\ng, customerUser1047, customer001, /orgs/1/sites/site002\ng, customerUser1047, customer001, /orgs/1/sites/site003\ng, customerUser1047, customer001, /orgs/1/sites/site004\ng, customerUser1047, customer001, /orgs/1/sites/site005\n\ng, customerUser1048,  customer001, /orgs/1/sites/site001\ng, customerUser1048,  customer001, /orgs/1/sites/site002\ng, customerUser1048,  customer001, /orgs/1/sites/site003\ng, customerUser1048,  customer001, /orgs/1/sites/site004\ng, customerUser1048,  customer001, /orgs/1/sites/site005\n\ng, customerUser1049,  customer001, /orgs/1/sites/site001\ng, customerUser1049,  customer001, /orgs/1/sites/site002\ng, customerUser1049,  customer001, /orgs/1/sites/site003\ng, customerUser1049,  customer001, /orgs/1/sites/site004\ng, customerUser1049,  customer001, /orgs/1/sites/site005\n\ng, customerUser1050,  customer001, /orgs/1/sites/site001\ng, customerUser1050,  customer001, /orgs/1/sites/site002\ng, customerUser1050,  customer001, /orgs/1/sites/site003\ng, customerUser1050,  customer001, /orgs/1/sites/site004\ng, customerUser1050,  customer001, /orgs/1/sites/site005\n\n# Group - customer001, / org1\ng, customerUser2001, customer001, /orgs/1/sites/site001\ng, customerUser2001, customer001, /orgs/1/sites/site002\ng, customerUser2001, customer001, /orgs/1/sites/site003\ng, customerUser2001, customer001, /orgs/1/sites/site004\ng, customerUser2001, customer001, /orgs/1/sites/site005\n\ng, customerUser2001, customer001, /orgs/1/sites/site001\ng, customerUser2001, customer001, /orgs/1/sites/site002\ng, customerUser2001, customer001, /orgs/1/sites/site003\ng, customerUser2001, customer001, /orgs/1/sites/site004\ng, customerUser2001, customer001, /orgs/1/sites/site005\n\ng, customerUser2003, customer001, /orgs/1/sites/site001\ng, customerUser2003, customer001, /orgs/1/sites/site002\ng, customerUser2003, customer001, /orgs/1/sites/site003\ng, customerUser2003, customer001, /orgs/1/sites/site004\ng, customerUser2003, customer001, /orgs/1/sites/site005\n\ng, customerUser2004, customer001, /orgs/1/sites/site001\ng, customerUser2004, customer001, /orgs/1/sites/site002\ng, customerUser2004, customer001, /orgs/1/sites/site003\ng, customerUser2004, customer001, /orgs/1/sites/site004\ng, customerUser2004, customer001, /orgs/1/sites/site005\n\ng, customerUser2005, customer001, /orgs/1/sites/site001\ng, customerUser2005, customer001, /orgs/1/sites/site002\ng, customerUser2005, customer001, /orgs/1/sites/site003\ng, customerUser2005, customer001, /orgs/1/sites/site004\ng, customerUser2005, customer001, /orgs/1/sites/site005\n\ng, customerUser2006, customer001, /orgs/1/sites/site001\ng, customerUser2006, customer001, /orgs/1/sites/site002\ng, customerUser2006, customer001, /orgs/1/sites/site003\ng, customerUser2006, customer001, /orgs/1/sites/site004\ng, customerUser2006, customer001, /orgs/1/sites/site005\n\ng, customerUser2007, customer001, /orgs/1/sites/site001\ng, customerUser2007, customer001, /orgs/1/sites/site002\ng, customerUser2007, customer001, /orgs/1/sites/site003\ng, customerUser2007, customer001, /orgs/1/sites/site004\ng, customerUser2007, customer001, /orgs/1/sites/site005\n\ng, customerUser2008,  customer001, /orgs/1/sites/site001\ng, customerUser2008,  customer001, /orgs/1/sites/site002\ng, customerUser2008,  customer001, /orgs/1/sites/site003\ng, customerUser2008,  customer001, /orgs/1/sites/site004\ng, customerUser2008,  customer001, /orgs/1/sites/site005\n\ng, customerUser2009,  customer001, /orgs/1/sites/site001\ng, customerUser2009,  customer001, /orgs/1/sites/site002\ng, customerUser2009,  customer001, /orgs/1/sites/site003\ng, customerUser2009,  customer001, /orgs/1/sites/site004\ng, customerUser2009,  customer001, /orgs/1/sites/site005\n\ng, customerUser2010,  customer001, /orgs/1/sites/site001\ng, customerUser2010,  customer001, /orgs/1/sites/site002\ng, customerUser2010,  customer001, /orgs/1/sites/site003\ng, customerUser2010,  customer001, /orgs/1/sites/site004\ng, customerUser2010,  customer001, /orgs/1/sites/site005\n\ng, customerUser2011,  customer001, /orgs/1/sites/site001\ng, customerUser2011,  customer001, /orgs/1/sites/site002\ng, customerUser2011,  customer001, /orgs/1/sites/site003\ng, customerUser2011,  customer001, /orgs/1/sites/site004\ng, customerUser2011,  customer001, /orgs/1/sites/site005\n\ng, customerUser2012,  customer001, /orgs/1/sites/site001\ng, customerUser2012,  customer001, /orgs/1/sites/site002\ng, customerUser2012,  customer001, /orgs/1/sites/site003\ng, customerUser2012,  customer001, /orgs/1/sites/site004\ng, customerUser2012,  customer001, /orgs/1/sites/site005\n\ng, customerUser2013, customer001, /orgs/1/sites/site001\ng, customerUser2013, customer001, /orgs/1/sites/site002\ng, customerUser2013, customer001, /orgs/1/sites/site003\ng, customerUser2013, customer001, /orgs/1/sites/site004\ng, customerUser2013, customer001, /orgs/1/sites/site005\n\ng, customerUser2014, customer001, /orgs/1/sites/site001\ng, customerUser2014, customer001, /orgs/1/sites/site002\ng, customerUser2014, customer001, /orgs/1/sites/site003\ng, customerUser2014, customer001, /orgs/1/sites/site004\ng, customerUser2014, customer001, /orgs/1/sites/site005\n\ng, customerUser2015, customer001, /orgs/1/sites/site001\ng, customerUser2015, customer001, /orgs/1/sites/site002\ng, customerUser2015, customer001, /orgs/1/sites/site003\ng, customerUser2015, customer001, /orgs/1/sites/site004\ng, customerUser2015, customer001, /orgs/1/sites/site005\n\ng, customerUser2016, customer001, /orgs/1/sites/site001\ng, customerUser2016, customer001, /orgs/1/sites/site002\ng, customerUser2016, customer001, /orgs/1/sites/site003\ng, customerUser2016, customer001, /orgs/1/sites/site004\ng, customerUser2016, customer001, /orgs/1/sites/site005\n\ng, customerUser2017, customer001, /orgs/1/sites/site001\ng, customerUser2017, customer001, /orgs/1/sites/site002\ng, customerUser2017, customer001, /orgs/1/sites/site003\ng, customerUser2017, customer001, /orgs/1/sites/site004\ng, customerUser2017, customer001, /orgs/1/sites/site005\n\ng, customerUser2018,  customer001, /orgs/1/sites/site001\ng, customerUser2018,  customer001, /orgs/1/sites/site002\ng, customerUser2018,  customer001, /orgs/1/sites/site003\ng, customerUser2018,  customer001, /orgs/1/sites/site004\ng, customerUser2018,  customer001, /orgs/1/sites/site005\n\ng, customerUser2019,  customer001, /orgs/1/sites/site001\ng, customerUser2019,  customer001, /orgs/1/sites/site002\ng, customerUser2019,  customer001, /orgs/1/sites/site003\ng, customerUser2019,  customer001, /orgs/1/sites/site004\ng, customerUser2019,  customer001, /orgs/1/sites/site005\n\ng, customerUser2020,  customer001, /orgs/1/sites/site001\ng, customerUser2020,  customer001, /orgs/1/sites/site002\ng, customerUser2020,  customer001, /orgs/1/sites/site003\ng, customerUser2020,  customer001, /orgs/1/sites/site004\ng, customerUser2020,  customer001, /orgs/1/sites/site005\n\ng, customerUser2021,  customer001, /orgs/1/sites/site001\ng, customerUser2021,  customer001, /orgs/1/sites/site002\ng, customerUser2021,  customer001, /orgs/1/sites/site003\ng, customerUser2021,  customer001, /orgs/1/sites/site004\ng, customerUser2021,  customer001, /orgs/1/sites/site005\n\ng, customerUser2022,  customer001, /orgs/1/sites/site001\ng, customerUser2022,  customer001, /orgs/1/sites/site002\ng, customerUser2022,  customer001, /orgs/1/sites/site003\ng, customerUser2022,  customer001, /orgs/1/sites/site004\ng, customerUser2022,  customer001, /orgs/1/sites/site005\n\ng, customerUser2023, customer001, /orgs/1/sites/site001\ng, customerUser2023, customer001, /orgs/1/sites/site002\ng, customerUser2023, customer001, /orgs/1/sites/site003\ng, customerUser2023, customer001, /orgs/1/sites/site004\ng, customerUser2023, customer001, /orgs/1/sites/site005\n\ng, customerUser2024, customer001, /orgs/1/sites/site001\ng, customerUser2024, customer001, /orgs/1/sites/site002\ng, customerUser2024, customer001, /orgs/1/sites/site003\ng, customerUser2024, customer001, /orgs/1/sites/site004\ng, customerUser2024, customer001, /orgs/1/sites/site005\n\ng, customerUser2025, customer001, /orgs/1/sites/site001\ng, customerUser2025, customer001, /orgs/1/sites/site002\ng, customerUser2025, customer001, /orgs/1/sites/site003\ng, customerUser2025, customer001, /orgs/1/sites/site004\ng, customerUser2025, customer001, /orgs/1/sites/site005\n\ng, customerUser2026, customer001, /orgs/1/sites/site001\ng, customerUser2026, customer001, /orgs/1/sites/site002\ng, customerUser2026, customer001, /orgs/1/sites/site003\ng, customerUser2026, customer001, /orgs/1/sites/site004\ng, customerUser2026, customer001, /orgs/1/sites/site005\n\ng, customerUser2027, customer001, /orgs/1/sites/site001\ng, customerUser2027, customer001, /orgs/1/sites/site002\ng, customerUser2027, customer001, /orgs/1/sites/site003\ng, customerUser2027, customer001, /orgs/1/sites/site004\ng, customerUser2027, customer001, /orgs/1/sites/site005\n\ng, customerUser2028,  customer001, /orgs/1/sites/site001\ng, customerUser2028,  customer001, /orgs/1/sites/site002\ng, customerUser2028,  customer001, /orgs/1/sites/site003\ng, customerUser2028,  customer001, /orgs/1/sites/site004\ng, customerUser2028,  customer001, /orgs/1/sites/site005\n\ng, customerUser2029,  customer001, /orgs/1/sites/site001\ng, customerUser2029,  customer001, /orgs/1/sites/site002\ng, customerUser2029,  customer001, /orgs/1/sites/site003\ng, customerUser2029,  customer001, /orgs/1/sites/site004\ng, customerUser2029,  customer001, /orgs/1/sites/site005\n\ng, customerUser2030,  customer001, /orgs/1/sites/site001\ng, customerUser2030,  customer001, /orgs/1/sites/site002\ng, customerUser2030,  customer001, /orgs/1/sites/site003\ng, customerUser2030,  customer001, /orgs/1/sites/site004\ng, customerUser2030,  customer001, /orgs/1/sites/site005\n\ng, customerUser2031,  customer001, /orgs/1/sites/site001\ng, customerUser2031,  customer001, /orgs/1/sites/site002\ng, customerUser2031,  customer001, /orgs/1/sites/site003\ng, customerUser2031,  customer001, /orgs/1/sites/site004\ng, customerUser2031,  customer001, /orgs/1/sites/site005\n\ng, customerUser2032,  customer001, /orgs/1/sites/site001\ng, customerUser2032,  customer001, /orgs/1/sites/site002\ng, customerUser2032,  customer001, /orgs/1/sites/site003\ng, customerUser2032,  customer001, /orgs/1/sites/site004\ng, customerUser2032,  customer001, /orgs/1/sites/site005\n\ng, customerUser2033, customer001, /orgs/1/sites/site001\ng, customerUser2033, customer001, /orgs/1/sites/site002\ng, customerUser2033, customer001, /orgs/1/sites/site003\ng, customerUser2033, customer001, /orgs/1/sites/site004\ng, customerUser2033, customer001, /orgs/1/sites/site005\n\ng, customerUser2034, customer001, /orgs/1/sites/site001\ng, customerUser2034, customer001, /orgs/1/sites/site002\ng, customerUser2034, customer001, /orgs/1/sites/site003\ng, customerUser2034, customer001, /orgs/1/sites/site004\ng, customerUser2034, customer001, /orgs/1/sites/site005\n\ng, customerUser2035, customer001, /orgs/1/sites/site001\ng, customerUser2035, customer001, /orgs/1/sites/site002\ng, customerUser2035, customer001, /orgs/1/sites/site003\ng, customerUser2035, customer001, /orgs/1/sites/site004\ng, customerUser2035, customer001, /orgs/1/sites/site005\n\ng, customerUser2036, customer001, /orgs/1/sites/site001\ng, customerUser2036, customer001, /orgs/1/sites/site002\ng, customerUser2036, customer001, /orgs/1/sites/site003\ng, customerUser2036, customer001, /orgs/1/sites/site004\ng, customerUser2036, customer001, /orgs/1/sites/site005\n\ng, customerUser2037, customer001, /orgs/1/sites/site001\ng, customerUser2037, customer001, /orgs/1/sites/site002\ng, customerUser2037, customer001, /orgs/1/sites/site003\ng, customerUser2037, customer001, /orgs/1/sites/site004\ng, customerUser2037, customer001, /orgs/1/sites/site005\n\ng, customerUser2038,  customer001, /orgs/1/sites/site001\ng, customerUser2038,  customer001, /orgs/1/sites/site002\ng, customerUser2038,  customer001, /orgs/1/sites/site003\ng, customerUser2038,  customer001, /orgs/1/sites/site004\ng, customerUser2038,  customer001, /orgs/1/sites/site005\n\ng, customerUser2039,  customer001, /orgs/1/sites/site001\ng, customerUser2039,  customer001, /orgs/1/sites/site002\ng, customerUser2039,  customer001, /orgs/1/sites/site003\ng, customerUser2039,  customer001, /orgs/1/sites/site004\ng, customerUser2039,  customer001, /orgs/1/sites/site005\n\ng, customerUser2040,  customer001, /orgs/1/sites/site001\ng, customerUser2040,  customer001, /orgs/1/sites/site002\ng, customerUser2040,  customer001, /orgs/1/sites/site003\ng, customerUser2040,  customer001, /orgs/1/sites/site004\ng, customerUser2040,  customer001, /orgs/1/sites/site005\n\ng, customerUser2041,  customer001, /orgs/1/sites/site001\ng, customerUser2041,  customer001, /orgs/1/sites/site002\ng, customerUser2041,  customer001, /orgs/1/sites/site003\ng, customerUser2041,  customer001, /orgs/1/sites/site004\ng, customerUser2041,  customer001, /orgs/1/sites/site005\n\ng, customerUser2042,  customer001, /orgs/1/sites/site001\ng, customerUser2042,  customer001, /orgs/1/sites/site002\ng, customerUser2042,  customer001, /orgs/1/sites/site003\ng, customerUser2042,  customer001, /orgs/1/sites/site004\ng, customerUser2042,  customer001, /orgs/1/sites/site005\n\ng, customerUser2043, customer001, /orgs/1/sites/site001\ng, customerUser2043, customer001, /orgs/1/sites/site002\ng, customerUser2043, customer001, /orgs/1/sites/site003\ng, customerUser2043, customer001, /orgs/1/sites/site004\ng, customerUser2043, customer001, /orgs/1/sites/site005\n\ng, customerUser2044, customer001, /orgs/1/sites/site001\ng, customerUser2044, customer001, /orgs/1/sites/site002\ng, customerUser2044, customer001, /orgs/1/sites/site003\ng, customerUser2044, customer001, /orgs/1/sites/site004\ng, customerUser2044, customer001, /orgs/1/sites/site005\n\ng, customerUser2045, customer001, /orgs/1/sites/site001\ng, customerUser2045, customer001, /orgs/1/sites/site002\ng, customerUser2045, customer001, /orgs/1/sites/site003\ng, customerUser2045, customer001, /orgs/1/sites/site004\ng, customerUser2045, customer001, /orgs/1/sites/site005\n\ng, customerUser2046, customer001, /orgs/1/sites/site001\ng, customerUser2046, customer001, /orgs/1/sites/site002\ng, customerUser2046, customer001, /orgs/1/sites/site003\ng, customerUser2046, customer001, /orgs/1/sites/site004\ng, customerUser2046, customer001, /orgs/1/sites/site005\n\ng, customerUser2047, customer001, /orgs/1/sites/site001\ng, customerUser2047, customer001, /orgs/1/sites/site002\ng, customerUser2047, customer001, /orgs/1/sites/site003\ng, customerUser2047, customer001, /orgs/1/sites/site004\ng, customerUser2047, customer001, /orgs/1/sites/site005\n\ng, customerUser2048,  customer001, /orgs/1/sites/site001\ng, customerUser2048,  customer001, /orgs/1/sites/site002\ng, customerUser2048,  customer001, /orgs/1/sites/site003\ng, customerUser2048,  customer001, /orgs/1/sites/site004\ng, customerUser2048,  customer001, /orgs/1/sites/site005\n\ng, customerUser2049,  customer001, /orgs/1/sites/site001\ng, customerUser2049,  customer001, /orgs/1/sites/site002\ng, customerUser2049,  customer001, /orgs/1/sites/site003\ng, customerUser2049,  customer001, /orgs/1/sites/site004\ng, customerUser2049,  customer001, /orgs/1/sites/site005\n\ng, customerUser2050,  customer001, /orgs/1/sites/site001\ng, customerUser2050,  customer001, /orgs/1/sites/site002\ng, customerUser2050,  customer001, /orgs/1/sites/site003\ng, customerUser2050,  customer001, /orgs/1/sites/site004\ng, customerUser2050,  customer001, /orgs/1/sites/site005\n\n# Group - staff001, / org2\ng, staffUser1001, staff001, /orgs/2/sites/site001\ng, staffUser1001, staff001, /orgs/2/sites/site002\ng, staffUser1001, staff001, /orgs/2/sites/site003\ng, staffUser1001, staff001, /orgs/2/sites/site004\ng, staffUser1001, staff001, /orgs/2/sites/site005\n\ng, staffUser1001, staff001, /orgs/2/sites/site001\ng, staffUser1001, staff001, /orgs/2/sites/site002\ng, staffUser1001, staff001, /orgs/2/sites/site003\ng, staffUser1001, staff001, /orgs/2/sites/site004\ng, staffUser1001, staff001, /orgs/2/sites/site005\n\ng, staffUser1003, staff001, /orgs/2/sites/site001\ng, staffUser1003, staff001, /orgs/2/sites/site002\ng, staffUser1003, staff001, /orgs/2/sites/site003\ng, staffUser1003, staff001, /orgs/2/sites/site004\ng, staffUser1003, staff001, /orgs/2/sites/site005\n\ng, staffUser1004, staff001, /orgs/2/sites/site001\ng, staffUser1004, staff001, /orgs/2/sites/site002\ng, staffUser1004, staff001, /orgs/2/sites/site003\ng, staffUser1004, staff001, /orgs/2/sites/site004\ng, staffUser1004, staff001, /orgs/2/sites/site005\n\ng, staffUser1005, staff001, /orgs/2/sites/site001\ng, staffUser1005, staff001, /orgs/2/sites/site002\ng, staffUser1005, staff001, /orgs/2/sites/site003\ng, staffUser1005, staff001, /orgs/2/sites/site004\ng, staffUser1005, staff001, /orgs/2/sites/site005\n\ng, staffUser1006, staff001, /orgs/2/sites/site001\ng, staffUser1006, staff001, /orgs/2/sites/site002\ng, staffUser1006, staff001, /orgs/2/sites/site003\ng, staffUser1006, staff001, /orgs/2/sites/site004\ng, staffUser1006, staff001, /orgs/2/sites/site005\n\ng, staffUser1007, staff001, /orgs/2/sites/site001\ng, staffUser1007, staff001, /orgs/2/sites/site002\ng, staffUser1007, staff001, /orgs/2/sites/site003\ng, staffUser1007, staff001, /orgs/2/sites/site004\ng, staffUser1007, staff001, /orgs/2/sites/site005\n\ng, staffUser1008,  staff001, /orgs/2/sites/site001\ng, staffUser1008,  staff001, /orgs/2/sites/site002\ng, staffUser1008,  staff001, /orgs/2/sites/site003\ng, staffUser1008,  staff001, /orgs/2/sites/site004\ng, staffUser1008,  staff001, /orgs/2/sites/site005\n\ng, staffUser1009,  staff001, /orgs/2/sites/site001\ng, staffUser1009,  staff001, /orgs/2/sites/site002\ng, staffUser1009,  staff001, /orgs/2/sites/site003\ng, staffUser1009,  staff001, /orgs/2/sites/site004\ng, staffUser1009,  staff001, /orgs/2/sites/site005\n\ng, staffUser1010,  staff001, /orgs/2/sites/site001\ng, staffUser1010,  staff001, /orgs/2/sites/site002\ng, staffUser1010,  staff001, /orgs/2/sites/site003\ng, staffUser1010,  staff001, /orgs/2/sites/site004\ng, staffUser1010,  staff001, /orgs/2/sites/site005\n\ng, staffUser1011,  staff001, /orgs/2/sites/site001\ng, staffUser1011,  staff001, /orgs/2/sites/site002\ng, staffUser1011,  staff001, /orgs/2/sites/site003\ng, staffUser1011,  staff001, /orgs/2/sites/site004\ng, staffUser1011,  staff001, /orgs/2/sites/site005\n\ng, staffUser1012,  staff001, /orgs/2/sites/site001\ng, staffUser1012,  staff001, /orgs/2/sites/site002\ng, staffUser1012,  staff001, /orgs/2/sites/site003\ng, staffUser1012,  staff001, /orgs/2/sites/site004\ng, staffUser1012,  staff001, /orgs/2/sites/site005\n\ng, staffUser1013, staff001, /orgs/2/sites/site001\ng, staffUser1013, staff001, /orgs/2/sites/site002\ng, staffUser1013, staff001, /orgs/2/sites/site003\ng, staffUser1013, staff001, /orgs/2/sites/site004\ng, staffUser1013, staff001, /orgs/2/sites/site005\n\ng, staffUser1014, staff001, /orgs/2/sites/site001\ng, staffUser1014, staff001, /orgs/2/sites/site002\ng, staffUser1014, staff001, /orgs/2/sites/site003\ng, staffUser1014, staff001, /orgs/2/sites/site004\ng, staffUser1014, staff001, /orgs/2/sites/site005\n\ng, staffUser1015, staff001, /orgs/2/sites/site001\ng, staffUser1015, staff001, /orgs/2/sites/site002\ng, staffUser1015, staff001, /orgs/2/sites/site003\ng, staffUser1015, staff001, /orgs/2/sites/site004\ng, staffUser1015, staff001, /orgs/2/sites/site005\n\ng, staffUser1016, staff001, /orgs/2/sites/site001\ng, staffUser1016, staff001, /orgs/2/sites/site002\ng, staffUser1016, staff001, /orgs/2/sites/site003\ng, staffUser1016, staff001, /orgs/2/sites/site004\ng, staffUser1016, staff001, /orgs/2/sites/site005\n\ng, staffUser1017, staff001, /orgs/2/sites/site001\ng, staffUser1017, staff001, /orgs/2/sites/site002\ng, staffUser1017, staff001, /orgs/2/sites/site003\ng, staffUser1017, staff001, /orgs/2/sites/site004\ng, staffUser1017, staff001, /orgs/2/sites/site005\n\ng, staffUser1018,  staff001, /orgs/2/sites/site001\ng, staffUser1018,  staff001, /orgs/2/sites/site002\ng, staffUser1018,  staff001, /orgs/2/sites/site003\ng, staffUser1018,  staff001, /orgs/2/sites/site004\ng, staffUser1018,  staff001, /orgs/2/sites/site005\n\ng, staffUser1019,  staff001, /orgs/2/sites/site001\ng, staffUser1019,  staff001, /orgs/2/sites/site002\ng, staffUser1019,  staff001, /orgs/2/sites/site003\ng, staffUser1019,  staff001, /orgs/2/sites/site004\ng, staffUser1019,  staff001, /orgs/2/sites/site005\n\ng, staffUser1020,  staff001, /orgs/2/sites/site001\ng, staffUser1020,  staff001, /orgs/2/sites/site002\ng, staffUser1020,  staff001, /orgs/2/sites/site003\ng, staffUser1020,  staff001, /orgs/2/sites/site004\ng, staffUser1020,  staff001, /orgs/2/sites/site005\n\ng, staffUser1021,  staff001, /orgs/2/sites/site001\ng, staffUser1021,  staff001, /orgs/2/sites/site002\ng, staffUser1021,  staff001, /orgs/2/sites/site003\ng, staffUser1021,  staff001, /orgs/2/sites/site004\ng, staffUser1021,  staff001, /orgs/2/sites/site005\n\ng, staffUser1022,  staff001, /orgs/2/sites/site001\ng, staffUser1022,  staff001, /orgs/2/sites/site002\ng, staffUser1022,  staff001, /orgs/2/sites/site003\ng, staffUser1022,  staff001, /orgs/2/sites/site004\ng, staffUser1022,  staff001, /orgs/2/sites/site005\n\ng, staffUser1023, staff001, /orgs/2/sites/site001\ng, staffUser1023, staff001, /orgs/2/sites/site002\ng, staffUser1023, staff001, /orgs/2/sites/site003\ng, staffUser1023, staff001, /orgs/2/sites/site004\ng, staffUser1023, staff001, /orgs/2/sites/site005\n\ng, staffUser1024, staff001, /orgs/2/sites/site001\ng, staffUser1024, staff001, /orgs/2/sites/site002\ng, staffUser1024, staff001, /orgs/2/sites/site003\ng, staffUser1024, staff001, /orgs/2/sites/site004\ng, staffUser1024, staff001, /orgs/2/sites/site005\n\ng, staffUser1025, staff001, /orgs/2/sites/site001\ng, staffUser1025, staff001, /orgs/2/sites/site002\ng, staffUser1025, staff001, /orgs/2/sites/site003\ng, staffUser1025, staff001, /orgs/2/sites/site004\ng, staffUser1025, staff001, /orgs/2/sites/site005\n\ng, staffUser1026, staff001, /orgs/2/sites/site001\ng, staffUser1026, staff001, /orgs/2/sites/site002\ng, staffUser1026, staff001, /orgs/2/sites/site003\ng, staffUser1026, staff001, /orgs/2/sites/site004\ng, staffUser1026, staff001, /orgs/2/sites/site005\n\ng, staffUser1027, staff001, /orgs/2/sites/site001\ng, staffUser1027, staff001, /orgs/2/sites/site002\ng, staffUser1027, staff001, /orgs/2/sites/site003\ng, staffUser1027, staff001, /orgs/2/sites/site004\ng, staffUser1027, staff001, /orgs/2/sites/site005\n\ng, staffUser1028,  staff001, /orgs/2/sites/site001\ng, staffUser1028,  staff001, /orgs/2/sites/site002\ng, staffUser1028,  staff001, /orgs/2/sites/site003\ng, staffUser1028,  staff001, /orgs/2/sites/site004\ng, staffUser1028,  staff001, /orgs/2/sites/site005\n\ng, staffUser1029,  staff001, /orgs/2/sites/site001\ng, staffUser1029,  staff001, /orgs/2/sites/site002\ng, staffUser1029,  staff001, /orgs/2/sites/site003\ng, staffUser1029,  staff001, /orgs/2/sites/site004\ng, staffUser1029,  staff001, /orgs/2/sites/site005\n\ng, staffUser1030,  staff001, /orgs/2/sites/site001\ng, staffUser1030,  staff001, /orgs/2/sites/site002\ng, staffUser1030,  staff001, /orgs/2/sites/site003\ng, staffUser1030,  staff001, /orgs/2/sites/site004\ng, staffUser1030,  staff001, /orgs/2/sites/site005\n\ng, staffUser1031,  staff001, /orgs/2/sites/site001\ng, staffUser1031,  staff001, /orgs/2/sites/site002\ng, staffUser1031,  staff001, /orgs/2/sites/site003\ng, staffUser1031,  staff001, /orgs/2/sites/site004\ng, staffUser1031,  staff001, /orgs/2/sites/site005\n\ng, staffUser1032,  staff001, /orgs/2/sites/site001\ng, staffUser1032,  staff001, /orgs/2/sites/site002\ng, staffUser1032,  staff001, /orgs/2/sites/site003\ng, staffUser1032,  staff001, /orgs/2/sites/site004\ng, staffUser1032,  staff001, /orgs/2/sites/site005\n\ng, staffUser1033, staff001, /orgs/2/sites/site001\ng, staffUser1033, staff001, /orgs/2/sites/site002\ng, staffUser1033, staff001, /orgs/2/sites/site003\ng, staffUser1033, staff001, /orgs/2/sites/site004\ng, staffUser1033, staff001, /orgs/2/sites/site005\n\ng, staffUser1034, staff001, /orgs/2/sites/site001\ng, staffUser1034, staff001, /orgs/2/sites/site002\ng, staffUser1034, staff001, /orgs/2/sites/site003\ng, staffUser1034, staff001, /orgs/2/sites/site004\ng, staffUser1034, staff001, /orgs/2/sites/site005\n\ng, staffUser1035, staff001, /orgs/2/sites/site001\ng, staffUser1035, staff001, /orgs/2/sites/site002\ng, staffUser1035, staff001, /orgs/2/sites/site003\ng, staffUser1035, staff001, /orgs/2/sites/site004\ng, staffUser1035, staff001, /orgs/2/sites/site005\n\ng, staffUser1036, staff001, /orgs/2/sites/site001\ng, staffUser1036, staff001, /orgs/2/sites/site002\ng, staffUser1036, staff001, /orgs/2/sites/site003\ng, staffUser1036, staff001, /orgs/2/sites/site004\ng, staffUser1036, staff001, /orgs/2/sites/site005\n\ng, staffUser1037, staff001, /orgs/2/sites/site001\ng, staffUser1037, staff001, /orgs/2/sites/site002\ng, staffUser1037, staff001, /orgs/2/sites/site003\ng, staffUser1037, staff001, /orgs/2/sites/site004\ng, staffUser1037, staff001, /orgs/2/sites/site005\n\ng, staffUser1038,  staff001, /orgs/2/sites/site001\ng, staffUser1038,  staff001, /orgs/2/sites/site002\ng, staffUser1038,  staff001, /orgs/2/sites/site003\ng, staffUser1038,  staff001, /orgs/2/sites/site004\ng, staffUser1038,  staff001, /orgs/2/sites/site005\n\ng, staffUser1039,  staff001, /orgs/2/sites/site001\ng, staffUser1039,  staff001, /orgs/2/sites/site002\ng, staffUser1039,  staff001, /orgs/2/sites/site003\ng, staffUser1039,  staff001, /orgs/2/sites/site004\ng, staffUser1039,  staff001, /orgs/2/sites/site005\n\ng, staffUser1040,  staff001, /orgs/2/sites/site001\ng, staffUser1040,  staff001, /orgs/2/sites/site002\ng, staffUser1040,  staff001, /orgs/2/sites/site003\ng, staffUser1040,  staff001, /orgs/2/sites/site004\ng, staffUser1040,  staff001, /orgs/2/sites/site005\n\ng, staffUser1041,  staff001, /orgs/2/sites/site001\ng, staffUser1041,  staff001, /orgs/2/sites/site002\ng, staffUser1041,  staff001, /orgs/2/sites/site003\ng, staffUser1041,  staff001, /orgs/2/sites/site004\ng, staffUser1041,  staff001, /orgs/2/sites/site005\n\ng, staffUser1042,  staff001, /orgs/2/sites/site001\ng, staffUser1042,  staff001, /orgs/2/sites/site002\ng, staffUser1042,  staff001, /orgs/2/sites/site003\ng, staffUser1042,  staff001, /orgs/2/sites/site004\ng, staffUser1042,  staff001, /orgs/2/sites/site005\n\ng, staffUser1043, staff001, /orgs/2/sites/site001\ng, staffUser1043, staff001, /orgs/2/sites/site002\ng, staffUser1043, staff001, /orgs/2/sites/site003\ng, staffUser1043, staff001, /orgs/2/sites/site004\ng, staffUser1043, staff001, /orgs/2/sites/site005\n\ng, staffUser1044, staff001, /orgs/2/sites/site001\ng, staffUser1044, staff001, /orgs/2/sites/site002\ng, staffUser1044, staff001, /orgs/2/sites/site003\ng, staffUser1044, staff001, /orgs/2/sites/site004\ng, staffUser1044, staff001, /orgs/2/sites/site005\n\ng, staffUser1045, staff001, /orgs/2/sites/site001\ng, staffUser1045, staff001, /orgs/2/sites/site002\ng, staffUser1045, staff001, /orgs/2/sites/site003\ng, staffUser1045, staff001, /orgs/2/sites/site004\ng, staffUser1045, staff001, /orgs/2/sites/site005\n\ng, staffUser1046, staff001, /orgs/2/sites/site001\ng, staffUser1046, staff001, /orgs/2/sites/site002\ng, staffUser1046, staff001, /orgs/2/sites/site003\ng, staffUser1046, staff001, /orgs/2/sites/site004\ng, staffUser1046, staff001, /orgs/2/sites/site005\n\ng, staffUser1047, staff001, /orgs/2/sites/site001\ng, staffUser1047, staff001, /orgs/2/sites/site002\ng, staffUser1047, staff001, /orgs/2/sites/site003\ng, staffUser1047, staff001, /orgs/2/sites/site004\ng, staffUser1047, staff001, /orgs/2/sites/site005\n\ng, staffUser1048,  staff001, /orgs/2/sites/site001\ng, staffUser1048,  staff001, /orgs/2/sites/site002\ng, staffUser1048,  staff001, /orgs/2/sites/site003\ng, staffUser1048,  staff001, /orgs/2/sites/site004\ng, staffUser1048,  staff001, /orgs/2/sites/site005\n\ng, staffUser1049,  staff001, /orgs/2/sites/site001\ng, staffUser1049,  staff001, /orgs/2/sites/site002\ng, staffUser1049,  staff001, /orgs/2/sites/site003\ng, staffUser1049,  staff001, /orgs/2/sites/site004\ng, staffUser1049,  staff001, /orgs/2/sites/site005\n\ng, staffUser1050,  staff001, /orgs/2/sites/site001\ng, staffUser1050,  staff001, /orgs/2/sites/site002\ng, staffUser1050,  staff001, /orgs/2/sites/site003\ng, staffUser1050,  staff001, /orgs/2/sites/site004\ng, staffUser1050,  staff001, /orgs/2/sites/site005\n\n# Group - staff001, / org2\ng, staffUser2001, staff001, /orgs/2/sites/site001\ng, staffUser2001, staff001, /orgs/2/sites/site002\ng, staffUser2001, staff001, /orgs/2/sites/site003\ng, staffUser2001, staff001, /orgs/2/sites/site004\ng, staffUser2001, staff001, /orgs/2/sites/site005\n\ng, staffUser2001, staff001, /orgs/2/sites/site001\ng, staffUser2001, staff001, /orgs/2/sites/site002\ng, staffUser2001, staff001, /orgs/2/sites/site003\ng, staffUser2001, staff001, /orgs/2/sites/site004\ng, staffUser2001, staff001, /orgs/2/sites/site005\n\ng, staffUser2003, staff001, /orgs/2/sites/site001\ng, staffUser2003, staff001, /orgs/2/sites/site002\ng, staffUser2003, staff001, /orgs/2/sites/site003\ng, staffUser2003, staff001, /orgs/2/sites/site004\ng, staffUser2003, staff001, /orgs/2/sites/site005\n\ng, staffUser2004, staff001, /orgs/2/sites/site001\ng, staffUser2004, staff001, /orgs/2/sites/site002\ng, staffUser2004, staff001, /orgs/2/sites/site003\ng, staffUser2004, staff001, /orgs/2/sites/site004\ng, staffUser2004, staff001, /orgs/2/sites/site005\n\ng, staffUser2005, staff001, /orgs/2/sites/site001\ng, staffUser2005, staff001, /orgs/2/sites/site002\ng, staffUser2005, staff001, /orgs/2/sites/site003\ng, staffUser2005, staff001, /orgs/2/sites/site004\ng, staffUser2005, staff001, /orgs/2/sites/site005\n\ng, staffUser2006, staff001, /orgs/2/sites/site001\ng, staffUser2006, staff001, /orgs/2/sites/site002\ng, staffUser2006, staff001, /orgs/2/sites/site003\ng, staffUser2006, staff001, /orgs/2/sites/site004\ng, staffUser2006, staff001, /orgs/2/sites/site005\n\ng, staffUser2007, staff001, /orgs/2/sites/site001\ng, staffUser2007, staff001, /orgs/2/sites/site002\ng, staffUser2007, staff001, /orgs/2/sites/site003\ng, staffUser2007, staff001, /orgs/2/sites/site004\ng, staffUser2007, staff001, /orgs/2/sites/site005\n\ng, staffUser2008,  staff001, /orgs/2/sites/site001\ng, staffUser2008,  staff001, /orgs/2/sites/site002\ng, staffUser2008,  staff001, /orgs/2/sites/site003\ng, staffUser2008,  staff001, /orgs/2/sites/site004\ng, staffUser2008,  staff001, /orgs/2/sites/site005\n\ng, staffUser2009,  staff001, /orgs/2/sites/site001\ng, staffUser2009,  staff001, /orgs/2/sites/site002\ng, staffUser2009,  staff001, /orgs/2/sites/site003\ng, staffUser2009,  staff001, /orgs/2/sites/site004\ng, staffUser2009,  staff001, /orgs/2/sites/site005\n\ng, staffUser2010,  staff001, /orgs/2/sites/site001\ng, staffUser2010,  staff001, /orgs/2/sites/site002\ng, staffUser2010,  staff001, /orgs/2/sites/site003\ng, staffUser2010,  staff001, /orgs/2/sites/site004\ng, staffUser2010,  staff001, /orgs/2/sites/site005\n\ng, staffUser2011,  staff001, /orgs/2/sites/site001\ng, staffUser2011,  staff001, /orgs/2/sites/site002\ng, staffUser2011,  staff001, /orgs/2/sites/site003\ng, staffUser2011,  staff001, /orgs/2/sites/site004\ng, staffUser2011,  staff001, /orgs/2/sites/site005\n\ng, staffUser2012,  staff001, /orgs/2/sites/site001\ng, staffUser2012,  staff001, /orgs/2/sites/site002\ng, staffUser2012,  staff001, /orgs/2/sites/site003\ng, staffUser2012,  staff001, /orgs/2/sites/site004\ng, staffUser2012,  staff001, /orgs/2/sites/site005\n\ng, staffUser2013, staff001, /orgs/2/sites/site001\ng, staffUser2013, staff001, /orgs/2/sites/site002\ng, staffUser2013, staff001, /orgs/2/sites/site003\ng, staffUser2013, staff001, /orgs/2/sites/site004\ng, staffUser2013, staff001, /orgs/2/sites/site005\n\ng, staffUser2014, staff001, /orgs/2/sites/site001\ng, staffUser2014, staff001, /orgs/2/sites/site002\ng, staffUser2014, staff001, /orgs/2/sites/site003\ng, staffUser2014, staff001, /orgs/2/sites/site004\ng, staffUser2014, staff001, /orgs/2/sites/site005\n\ng, staffUser2015, staff001, /orgs/2/sites/site001\ng, staffUser2015, staff001, /orgs/2/sites/site002\ng, staffUser2015, staff001, /orgs/2/sites/site003\ng, staffUser2015, staff001, /orgs/2/sites/site004\ng, staffUser2015, staff001, /orgs/2/sites/site005\n\ng, staffUser2016, staff001, /orgs/2/sites/site001\ng, staffUser2016, staff001, /orgs/2/sites/site002\ng, staffUser2016, staff001, /orgs/2/sites/site003\ng, staffUser2016, staff001, /orgs/2/sites/site004\ng, staffUser2016, staff001, /orgs/2/sites/site005\n\ng, staffUser2017, staff001, /orgs/2/sites/site001\ng, staffUser2017, staff001, /orgs/2/sites/site002\ng, staffUser2017, staff001, /orgs/2/sites/site003\ng, staffUser2017, staff001, /orgs/2/sites/site004\ng, staffUser2017, staff001, /orgs/2/sites/site005\n\ng, staffUser2018,  staff001, /orgs/2/sites/site001\ng, staffUser2018,  staff001, /orgs/2/sites/site002\ng, staffUser2018,  staff001, /orgs/2/sites/site003\ng, staffUser2018,  staff001, /orgs/2/sites/site004\ng, staffUser2018,  staff001, /orgs/2/sites/site005\n\ng, staffUser2019,  staff001, /orgs/2/sites/site001\ng, staffUser2019,  staff001, /orgs/2/sites/site002\ng, staffUser2019,  staff001, /orgs/2/sites/site003\ng, staffUser2019,  staff001, /orgs/2/sites/site004\ng, staffUser2019,  staff001, /orgs/2/sites/site005\n\ng, staffUser2020,  staff001, /orgs/2/sites/site001\ng, staffUser2020,  staff001, /orgs/2/sites/site002\ng, staffUser2020,  staff001, /orgs/2/sites/site003\ng, staffUser2020,  staff001, /orgs/2/sites/site004\ng, staffUser2020,  staff001, /orgs/2/sites/site005\n\ng, staffUser2021,  staff001, /orgs/2/sites/site001\ng, staffUser2021,  staff001, /orgs/2/sites/site002\ng, staffUser2021,  staff001, /orgs/2/sites/site003\ng, staffUser2021,  staff001, /orgs/2/sites/site004\ng, staffUser2021,  staff001, /orgs/2/sites/site005\n\ng, staffUser2022,  staff001, /orgs/2/sites/site001\ng, staffUser2022,  staff001, /orgs/2/sites/site002\ng, staffUser2022,  staff001, /orgs/2/sites/site003\ng, staffUser2022,  staff001, /orgs/2/sites/site004\ng, staffUser2022,  staff001, /orgs/2/sites/site005\n\ng, staffUser2023, staff001, /orgs/2/sites/site001\ng, staffUser2023, staff001, /orgs/2/sites/site002\ng, staffUser2023, staff001, /orgs/2/sites/site003\ng, staffUser2023, staff001, /orgs/2/sites/site004\ng, staffUser2023, staff001, /orgs/2/sites/site005\n\ng, staffUser2024, staff001, /orgs/2/sites/site001\ng, staffUser2024, staff001, /orgs/2/sites/site002\ng, staffUser2024, staff001, /orgs/2/sites/site003\ng, staffUser2024, staff001, /orgs/2/sites/site004\ng, staffUser2024, staff001, /orgs/2/sites/site005\n\ng, staffUser2025, staff001, /orgs/2/sites/site001\ng, staffUser2025, staff001, /orgs/2/sites/site002\ng, staffUser2025, staff001, /orgs/2/sites/site003\ng, staffUser2025, staff001, /orgs/2/sites/site004\ng, staffUser2025, staff001, /orgs/2/sites/site005\n\ng, staffUser2026, staff001, /orgs/2/sites/site001\ng, staffUser2026, staff001, /orgs/2/sites/site002\ng, staffUser2026, staff001, /orgs/2/sites/site003\ng, staffUser2026, staff001, /orgs/2/sites/site004\ng, staffUser2026, staff001, /orgs/2/sites/site005\n\ng, staffUser2027, staff001, /orgs/2/sites/site001\ng, staffUser2027, staff001, /orgs/2/sites/site002\ng, staffUser2027, staff001, /orgs/2/sites/site003\ng, staffUser2027, staff001, /orgs/2/sites/site004\ng, staffUser2027, staff001, /orgs/2/sites/site005\n\ng, staffUser2028,  staff001, /orgs/2/sites/site001\ng, staffUser2028,  staff001, /orgs/2/sites/site002\ng, staffUser2028,  staff001, /orgs/2/sites/site003\ng, staffUser2028,  staff001, /orgs/2/sites/site004\ng, staffUser2028,  staff001, /orgs/2/sites/site005\n\ng, staffUser2029,  staff001, /orgs/2/sites/site001\ng, staffUser2029,  staff001, /orgs/2/sites/site002\ng, staffUser2029,  staff001, /orgs/2/sites/site003\ng, staffUser2029,  staff001, /orgs/2/sites/site004\ng, staffUser2029,  staff001, /orgs/2/sites/site005\n\ng, staffUser2030,  staff001, /orgs/2/sites/site001\ng, staffUser2030,  staff001, /orgs/2/sites/site002\ng, staffUser2030,  staff001, /orgs/2/sites/site003\ng, staffUser2030,  staff001, /orgs/2/sites/site004\ng, staffUser2030,  staff001, /orgs/2/sites/site005\n\ng, staffUser2031,  staff001, /orgs/2/sites/site001\ng, staffUser2031,  staff001, /orgs/2/sites/site002\ng, staffUser2031,  staff001, /orgs/2/sites/site003\ng, staffUser2031,  staff001, /orgs/2/sites/site004\ng, staffUser2031,  staff001, /orgs/2/sites/site005\n\ng, staffUser2032,  staff001, /orgs/2/sites/site001\ng, staffUser2032,  staff001, /orgs/2/sites/site002\ng, staffUser2032,  staff001, /orgs/2/sites/site003\ng, staffUser2032,  staff001, /orgs/2/sites/site004\ng, staffUser2032,  staff001, /orgs/2/sites/site005\n\ng, staffUser2033, staff001, /orgs/2/sites/site001\ng, staffUser2033, staff001, /orgs/2/sites/site002\ng, staffUser2033, staff001, /orgs/2/sites/site003\ng, staffUser2033, staff001, /orgs/2/sites/site004\ng, staffUser2033, staff001, /orgs/2/sites/site005\n\ng, staffUser2034, staff001, /orgs/2/sites/site001\ng, staffUser2034, staff001, /orgs/2/sites/site002\ng, staffUser2034, staff001, /orgs/2/sites/site003\ng, staffUser2034, staff001, /orgs/2/sites/site004\ng, staffUser2034, staff001, /orgs/2/sites/site005\n\ng, staffUser2035, staff001, /orgs/2/sites/site001\ng, staffUser2035, staff001, /orgs/2/sites/site002\ng, staffUser2035, staff001, /orgs/2/sites/site003\ng, staffUser2035, staff001, /orgs/2/sites/site004\ng, staffUser2035, staff001, /orgs/2/sites/site005\n\ng, staffUser2036, staff001, /orgs/2/sites/site001\ng, staffUser2036, staff001, /orgs/2/sites/site002\ng, staffUser2036, staff001, /orgs/2/sites/site003\ng, staffUser2036, staff001, /orgs/2/sites/site004\ng, staffUser2036, staff001, /orgs/2/sites/site005\n\ng, staffUser2037, staff001, /orgs/2/sites/site001\ng, staffUser2037, staff001, /orgs/2/sites/site002\ng, staffUser2037, staff001, /orgs/2/sites/site003\ng, staffUser2037, staff001, /orgs/2/sites/site004\ng, staffUser2037, staff001, /orgs/2/sites/site005\n\ng, staffUser2038,  staff001, /orgs/2/sites/site001\ng, staffUser2038,  staff001, /orgs/2/sites/site002\ng, staffUser2038,  staff001, /orgs/2/sites/site003\ng, staffUser2038,  staff001, /orgs/2/sites/site004\ng, staffUser2038,  staff001, /orgs/2/sites/site005\n\ng, staffUser2039,  staff001, /orgs/2/sites/site001\ng, staffUser2039,  staff001, /orgs/2/sites/site002\ng, staffUser2039,  staff001, /orgs/2/sites/site003\ng, staffUser2039,  staff001, /orgs/2/sites/site004\ng, staffUser2039,  staff001, /orgs/2/sites/site005\n\ng, staffUser2040,  staff001, /orgs/2/sites/site001\ng, staffUser2040,  staff001, /orgs/2/sites/site002\ng, staffUser2040,  staff001, /orgs/2/sites/site003\ng, staffUser2040,  staff001, /orgs/2/sites/site004\ng, staffUser2040,  staff001, /orgs/2/sites/site005\n\ng, staffUser2041,  staff001, /orgs/2/sites/site001\ng, staffUser2041,  staff001, /orgs/2/sites/site002\ng, staffUser2041,  staff001, /orgs/2/sites/site003\ng, staffUser2041,  staff001, /orgs/2/sites/site004\ng, staffUser2041,  staff001, /orgs/2/sites/site005\n\ng, staffUser2042,  staff001, /orgs/2/sites/site001\ng, staffUser2042,  staff001, /orgs/2/sites/site002\ng, staffUser2042,  staff001, /orgs/2/sites/site003\ng, staffUser2042,  staff001, /orgs/2/sites/site004\ng, staffUser2042,  staff001, /orgs/2/sites/site005\n\ng, staffUser2043, staff001, /orgs/2/sites/site001\ng, staffUser2043, staff001, /orgs/2/sites/site002\ng, staffUser2043, staff001, /orgs/2/sites/site003\ng, staffUser2043, staff001, /orgs/2/sites/site004\ng, staffUser2043, staff001, /orgs/2/sites/site005\n\ng, staffUser2044, staff001, /orgs/2/sites/site001\ng, staffUser2044, staff001, /orgs/2/sites/site002\ng, staffUser2044, staff001, /orgs/2/sites/site003\ng, staffUser2044, staff001, /orgs/2/sites/site004\ng, staffUser2044, staff001, /orgs/2/sites/site005\n\ng, staffUser2045, staff001, /orgs/2/sites/site001\ng, staffUser2045, staff001, /orgs/2/sites/site002\ng, staffUser2045, staff001, /orgs/2/sites/site003\ng, staffUser2045, staff001, /orgs/2/sites/site004\ng, staffUser2045, staff001, /orgs/2/sites/site005\n\ng, staffUser2046, staff001, /orgs/2/sites/site001\ng, staffUser2046, staff001, /orgs/2/sites/site002\ng, staffUser2046, staff001, /orgs/2/sites/site003\ng, staffUser2046, staff001, /orgs/2/sites/site004\ng, staffUser2046, staff001, /orgs/2/sites/site005\n\ng, staffUser2047, staff001, /orgs/2/sites/site001\ng, staffUser2047, staff001, /orgs/2/sites/site002\ng, staffUser2047, staff001, /orgs/2/sites/site003\ng, staffUser2047, staff001, /orgs/2/sites/site004\ng, staffUser2047, staff001, /orgs/2/sites/site005\n\ng, staffUser2048,  staff001, /orgs/2/sites/site001\ng, staffUser2048,  staff001, /orgs/2/sites/site002\ng, staffUser2048,  staff001, /orgs/2/sites/site003\ng, staffUser2048,  staff001, /orgs/2/sites/site004\ng, staffUser2048,  staff001, /orgs/2/sites/site005\n\ng, staffUser2049,  staff001, /orgs/2/sites/site001\ng, staffUser2049,  staff001, /orgs/2/sites/site002\ng, staffUser2049,  staff001, /orgs/2/sites/site003\ng, staffUser2049,  staff001, /orgs/2/sites/site004\ng, staffUser2049,  staff001, /orgs/2/sites/site005\n\ng, staffUser2050,  staff001, /orgs/2/sites/site001\ng, staffUser2050,  staff001, /orgs/2/sites/site002\ng, staffUser2050,  staff001, /orgs/2/sites/site003\ng, staffUser2050,  staff001, /orgs/2/sites/site004\ng, staffUser2050,  staff001, /orgs/2/sites/site005\n\n# Group - manager001, / org2\ng, managerUser1001, manager001, /orgs/2/sites/site001\ng, managerUser1001, manager001, /orgs/2/sites/site002\ng, managerUser1001, manager001, /orgs/2/sites/site003\ng, managerUser1001, manager001, /orgs/2/sites/site004\ng, managerUser1001, manager001, /orgs/2/sites/site005\n\ng, managerUser1001, manager001, /orgs/2/sites/site001\ng, managerUser1001, manager001, /orgs/2/sites/site002\ng, managerUser1001, manager001, /orgs/2/sites/site003\ng, managerUser1001, manager001, /orgs/2/sites/site004\ng, managerUser1001, manager001, /orgs/2/sites/site005\n\ng, managerUser1003, manager001, /orgs/2/sites/site001\ng, managerUser1003, manager001, /orgs/2/sites/site002\ng, managerUser1003, manager001, /orgs/2/sites/site003\ng, managerUser1003, manager001, /orgs/2/sites/site004\ng, managerUser1003, manager001, /orgs/2/sites/site005\n\ng, managerUser1004, manager001, /orgs/2/sites/site001\ng, managerUser1004, manager001, /orgs/2/sites/site002\ng, managerUser1004, manager001, /orgs/2/sites/site003\ng, managerUser1004, manager001, /orgs/2/sites/site004\ng, managerUser1004, manager001, /orgs/2/sites/site005\n\ng, managerUser1005, manager001, /orgs/2/sites/site001\ng, managerUser1005, manager001, /orgs/2/sites/site002\ng, managerUser1005, manager001, /orgs/2/sites/site003\ng, managerUser1005, manager001, /orgs/2/sites/site004\ng, managerUser1005, manager001, /orgs/2/sites/site005\n\ng, managerUser1006, manager001, /orgs/2/sites/site001\ng, managerUser1006, manager001, /orgs/2/sites/site002\ng, managerUser1006, manager001, /orgs/2/sites/site003\ng, managerUser1006, manager001, /orgs/2/sites/site004\ng, managerUser1006, manager001, /orgs/2/sites/site005\n\ng, managerUser1007, manager001, /orgs/2/sites/site001\ng, managerUser1007, manager001, /orgs/2/sites/site002\ng, managerUser1007, manager001, /orgs/2/sites/site003\ng, managerUser1007, manager001, /orgs/2/sites/site004\ng, managerUser1007, manager001, /orgs/2/sites/site005\n\ng, managerUser1008,  manager001, /orgs/2/sites/site001\ng, managerUser1008,  manager001, /orgs/2/sites/site002\ng, managerUser1008,  manager001, /orgs/2/sites/site003\ng, managerUser1008,  manager001, /orgs/2/sites/site004\ng, managerUser1008,  manager001, /orgs/2/sites/site005\n\ng, managerUser1009,  manager001, /orgs/2/sites/site001\ng, managerUser1009,  manager001, /orgs/2/sites/site002\ng, managerUser1009,  manager001, /orgs/2/sites/site003\ng, managerUser1009,  manager001, /orgs/2/sites/site004\ng, managerUser1009,  manager001, /orgs/2/sites/site005\n\ng, managerUser1010,  manager001, /orgs/2/sites/site001\ng, managerUser1010,  manager001, /orgs/2/sites/site002\ng, managerUser1010,  manager001, /orgs/2/sites/site003\ng, managerUser1010,  manager001, /orgs/2/sites/site004\ng, managerUser1010,  manager001, /orgs/2/sites/site005\n\ng, managerUser1011,  manager001, /orgs/2/sites/site001\ng, managerUser1011,  manager001, /orgs/2/sites/site002\ng, managerUser1011,  manager001, /orgs/2/sites/site003\ng, managerUser1011,  manager001, /orgs/2/sites/site004\ng, managerUser1011,  manager001, /orgs/2/sites/site005\n\ng, managerUser1012,  manager001, /orgs/2/sites/site001\ng, managerUser1012,  manager001, /orgs/2/sites/site002\ng, managerUser1012,  manager001, /orgs/2/sites/site003\ng, managerUser1012,  manager001, /orgs/2/sites/site004\ng, managerUser1012,  manager001, /orgs/2/sites/site005\n\ng, managerUser1013, manager001, /orgs/2/sites/site001\ng, managerUser1013, manager001, /orgs/2/sites/site002\ng, managerUser1013, manager001, /orgs/2/sites/site003\ng, managerUser1013, manager001, /orgs/2/sites/site004\ng, managerUser1013, manager001, /orgs/2/sites/site005\n\ng, managerUser1014, manager001, /orgs/2/sites/site001\ng, managerUser1014, manager001, /orgs/2/sites/site002\ng, managerUser1014, manager001, /orgs/2/sites/site003\ng, managerUser1014, manager001, /orgs/2/sites/site004\ng, managerUser1014, manager001, /orgs/2/sites/site005\n\ng, managerUser1015, manager001, /orgs/2/sites/site001\ng, managerUser1015, manager001, /orgs/2/sites/site002\ng, managerUser1015, manager001, /orgs/2/sites/site003\ng, managerUser1015, manager001, /orgs/2/sites/site004\ng, managerUser1015, manager001, /orgs/2/sites/site005\n\ng, managerUser1016, manager001, /orgs/2/sites/site001\ng, managerUser1016, manager001, /orgs/2/sites/site002\ng, managerUser1016, manager001, /orgs/2/sites/site003\ng, managerUser1016, manager001, /orgs/2/sites/site004\ng, managerUser1016, manager001, /orgs/2/sites/site005\n\ng, managerUser1017, manager001, /orgs/2/sites/site001\ng, managerUser1017, manager001, /orgs/2/sites/site002\ng, managerUser1017, manager001, /orgs/2/sites/site003\ng, managerUser1017, manager001, /orgs/2/sites/site004\ng, managerUser1017, manager001, /orgs/2/sites/site005\n\ng, managerUser1018,  manager001, /orgs/2/sites/site001\ng, managerUser1018,  manager001, /orgs/2/sites/site002\ng, managerUser1018,  manager001, /orgs/2/sites/site003\ng, managerUser1018,  manager001, /orgs/2/sites/site004\ng, managerUser1018,  manager001, /orgs/2/sites/site005\n\ng, managerUser1019,  manager001, /orgs/2/sites/site001\ng, managerUser1019,  manager001, /orgs/2/sites/site002\ng, managerUser1019,  manager001, /orgs/2/sites/site003\ng, managerUser1019,  manager001, /orgs/2/sites/site004\ng, managerUser1019,  manager001, /orgs/2/sites/site005\n\ng, managerUser1020,  manager001, /orgs/2/sites/site001\ng, managerUser1020,  manager001, /orgs/2/sites/site002\ng, managerUser1020,  manager001, /orgs/2/sites/site003\ng, managerUser1020,  manager001, /orgs/2/sites/site004\ng, managerUser1020,  manager001, /orgs/2/sites/site005\n\ng, managerUser1021,  manager001, /orgs/2/sites/site001\ng, managerUser1021,  manager001, /orgs/2/sites/site002\ng, managerUser1021,  manager001, /orgs/2/sites/site003\ng, managerUser1021,  manager001, /orgs/2/sites/site004\ng, managerUser1021,  manager001, /orgs/2/sites/site005\n\ng, managerUser1022,  manager001, /orgs/2/sites/site001\ng, managerUser1022,  manager001, /orgs/2/sites/site002\ng, managerUser1022,  manager001, /orgs/2/sites/site003\ng, managerUser1022,  manager001, /orgs/2/sites/site004\ng, managerUser1022,  manager001, /orgs/2/sites/site005\n\ng, managerUser1023, manager001, /orgs/2/sites/site001\ng, managerUser1023, manager001, /orgs/2/sites/site002\ng, managerUser1023, manager001, /orgs/2/sites/site003\ng, managerUser1023, manager001, /orgs/2/sites/site004\ng, managerUser1023, manager001, /orgs/2/sites/site005\n\ng, managerUser1024, manager001, /orgs/2/sites/site001\ng, managerUser1024, manager001, /orgs/2/sites/site002\ng, managerUser1024, manager001, /orgs/2/sites/site003\ng, managerUser1024, manager001, /orgs/2/sites/site004\ng, managerUser1024, manager001, /orgs/2/sites/site005\n\ng, managerUser1025, manager001, /orgs/2/sites/site001\ng, managerUser1025, manager001, /orgs/2/sites/site002\ng, managerUser1025, manager001, /orgs/2/sites/site003\ng, managerUser1025, manager001, /orgs/2/sites/site004\ng, managerUser1025, manager001, /orgs/2/sites/site005\n\ng, managerUser1026, manager001, /orgs/2/sites/site001\ng, managerUser1026, manager001, /orgs/2/sites/site002\ng, managerUser1026, manager001, /orgs/2/sites/site003\ng, managerUser1026, manager001, /orgs/2/sites/site004\ng, managerUser1026, manager001, /orgs/2/sites/site005\n\ng, managerUser1027, manager001, /orgs/2/sites/site001\ng, managerUser1027, manager001, /orgs/2/sites/site002\ng, managerUser1027, manager001, /orgs/2/sites/site003\ng, managerUser1027, manager001, /orgs/2/sites/site004\ng, managerUser1027, manager001, /orgs/2/sites/site005\n\ng, managerUser1028,  manager001, /orgs/2/sites/site001\ng, managerUser1028,  manager001, /orgs/2/sites/site002\ng, managerUser1028,  manager001, /orgs/2/sites/site003\ng, managerUser1028,  manager001, /orgs/2/sites/site004\ng, managerUser1028,  manager001, /orgs/2/sites/site005\n\ng, managerUser1029,  manager001, /orgs/2/sites/site001\ng, managerUser1029,  manager001, /orgs/2/sites/site002\ng, managerUser1029,  manager001, /orgs/2/sites/site003\ng, managerUser1029,  manager001, /orgs/2/sites/site004\ng, managerUser1029,  manager001, /orgs/2/sites/site005\n\ng, managerUser1030,  manager001, /orgs/2/sites/site001\ng, managerUser1030,  manager001, /orgs/2/sites/site002\ng, managerUser1030,  manager001, /orgs/2/sites/site003\ng, managerUser1030,  manager001, /orgs/2/sites/site004\ng, managerUser1030,  manager001, /orgs/2/sites/site005\n\ng, managerUser1031,  manager001, /orgs/2/sites/site001\ng, managerUser1031,  manager001, /orgs/2/sites/site002\ng, managerUser1031,  manager001, /orgs/2/sites/site003\ng, managerUser1031,  manager001, /orgs/2/sites/site004\ng, managerUser1031,  manager001, /orgs/2/sites/site005\n\ng, managerUser1032,  manager001, /orgs/2/sites/site001\ng, managerUser1032,  manager001, /orgs/2/sites/site002\ng, managerUser1032,  manager001, /orgs/2/sites/site003\ng, managerUser1032,  manager001, /orgs/2/sites/site004\ng, managerUser1032,  manager001, /orgs/2/sites/site005\n\ng, managerUser1033, manager001, /orgs/2/sites/site001\ng, managerUser1033, manager001, /orgs/2/sites/site002\ng, managerUser1033, manager001, /orgs/2/sites/site003\ng, managerUser1033, manager001, /orgs/2/sites/site004\ng, managerUser1033, manager001, /orgs/2/sites/site005\n\ng, managerUser1034, manager001, /orgs/2/sites/site001\ng, managerUser1034, manager001, /orgs/2/sites/site002\ng, managerUser1034, manager001, /orgs/2/sites/site003\ng, managerUser1034, manager001, /orgs/2/sites/site004\ng, managerUser1034, manager001, /orgs/2/sites/site005\n\ng, managerUser1035, manager001, /orgs/2/sites/site001\ng, managerUser1035, manager001, /orgs/2/sites/site002\ng, managerUser1035, manager001, /orgs/2/sites/site003\ng, managerUser1035, manager001, /orgs/2/sites/site004\ng, managerUser1035, manager001, /orgs/2/sites/site005\n\ng, managerUser1036, manager001, /orgs/2/sites/site001\ng, managerUser1036, manager001, /orgs/2/sites/site002\ng, managerUser1036, manager001, /orgs/2/sites/site003\ng, managerUser1036, manager001, /orgs/2/sites/site004\ng, managerUser1036, manager001, /orgs/2/sites/site005\n\ng, managerUser1037, manager001, /orgs/2/sites/site001\ng, managerUser1037, manager001, /orgs/2/sites/site002\ng, managerUser1037, manager001, /orgs/2/sites/site003\ng, managerUser1037, manager001, /orgs/2/sites/site004\ng, managerUser1037, manager001, /orgs/2/sites/site005\n\ng, managerUser1038,  manager001, /orgs/2/sites/site001\ng, managerUser1038,  manager001, /orgs/2/sites/site002\ng, managerUser1038,  manager001, /orgs/2/sites/site003\ng, managerUser1038,  manager001, /orgs/2/sites/site004\ng, managerUser1038,  manager001, /orgs/2/sites/site005\n\ng, managerUser1039,  manager001, /orgs/2/sites/site001\ng, managerUser1039,  manager001, /orgs/2/sites/site002\ng, managerUser1039,  manager001, /orgs/2/sites/site003\ng, managerUser1039,  manager001, /orgs/2/sites/site004\ng, managerUser1039,  manager001, /orgs/2/sites/site005\n\ng, managerUser1040,  manager001, /orgs/2/sites/site001\ng, managerUser1040,  manager001, /orgs/2/sites/site002\ng, managerUser1040,  manager001, /orgs/2/sites/site003\ng, managerUser1040,  manager001, /orgs/2/sites/site004\ng, managerUser1040,  manager001, /orgs/2/sites/site005\n\ng, managerUser1041,  manager001, /orgs/2/sites/site001\ng, managerUser1041,  manager001, /orgs/2/sites/site002\ng, managerUser1041,  manager001, /orgs/2/sites/site003\ng, managerUser1041,  manager001, /orgs/2/sites/site004\ng, managerUser1041,  manager001, /orgs/2/sites/site005\n\ng, managerUser1042,  manager001, /orgs/2/sites/site001\ng, managerUser1042,  manager001, /orgs/2/sites/site002\ng, managerUser1042,  manager001, /orgs/2/sites/site003\ng, managerUser1042,  manager001, /orgs/2/sites/site004\ng, managerUser1042,  manager001, /orgs/2/sites/site005\n\ng, managerUser1043, manager001, /orgs/2/sites/site001\ng, managerUser1043, manager001, /orgs/2/sites/site002\ng, managerUser1043, manager001, /orgs/2/sites/site003\ng, managerUser1043, manager001, /orgs/2/sites/site004\ng, managerUser1043, manager001, /orgs/2/sites/site005\n\ng, managerUser1044, manager001, /orgs/2/sites/site001\ng, managerUser1044, manager001, /orgs/2/sites/site002\ng, managerUser1044, manager001, /orgs/2/sites/site003\ng, managerUser1044, manager001, /orgs/2/sites/site004\ng, managerUser1044, manager001, /orgs/2/sites/site005\n\ng, managerUser1045, manager001, /orgs/2/sites/site001\ng, managerUser1045, manager001, /orgs/2/sites/site002\ng, managerUser1045, manager001, /orgs/2/sites/site003\ng, managerUser1045, manager001, /orgs/2/sites/site004\ng, managerUser1045, manager001, /orgs/2/sites/site005\n\ng, managerUser1046, manager001, /orgs/2/sites/site001\ng, managerUser1046, manager001, /orgs/2/sites/site002\ng, managerUser1046, manager001, /orgs/2/sites/site003\ng, managerUser1046, manager001, /orgs/2/sites/site004\ng, managerUser1046, manager001, /orgs/2/sites/site005\n\ng, managerUser1047, manager001, /orgs/2/sites/site001\ng, managerUser1047, manager001, /orgs/2/sites/site002\ng, managerUser1047, manager001, /orgs/2/sites/site003\ng, managerUser1047, manager001, /orgs/2/sites/site004\ng, managerUser1047, manager001, /orgs/2/sites/site005\n\ng, managerUser1048,  manager001, /orgs/2/sites/site001\ng, managerUser1048,  manager001, /orgs/2/sites/site002\ng, managerUser1048,  manager001, /orgs/2/sites/site003\ng, managerUser1048,  manager001, /orgs/2/sites/site004\ng, managerUser1048,  manager001, /orgs/2/sites/site005\n\ng, managerUser1049,  manager001, /orgs/2/sites/site001\ng, managerUser1049,  manager001, /orgs/2/sites/site002\ng, managerUser1049,  manager001, /orgs/2/sites/site003\ng, managerUser1049,  manager001, /orgs/2/sites/site004\ng, managerUser1049,  manager001, /orgs/2/sites/site005\n\ng, managerUser1050,  manager001, /orgs/2/sites/site001\ng, managerUser1050,  manager001, /orgs/2/sites/site002\ng, managerUser1050,  manager001, /orgs/2/sites/site003\ng, managerUser1050,  manager001, /orgs/2/sites/site004\ng, managerUser1050,  manager001, /orgs/2/sites/site005\n\n# Group - manager001, / org2\ng, managerUser2001, manager001, /orgs/2/sites/site001\ng, managerUser2001, manager001, /orgs/2/sites/site002\ng, managerUser2001, manager001, /orgs/2/sites/site003\ng, managerUser2001, manager001, /orgs/2/sites/site004\ng, managerUser2001, manager001, /orgs/2/sites/site005\n\ng, managerUser2001, manager001, /orgs/2/sites/site001\ng, managerUser2001, manager001, /orgs/2/sites/site002\ng, managerUser2001, manager001, /orgs/2/sites/site003\ng, managerUser2001, manager001, /orgs/2/sites/site004\ng, managerUser2001, manager001, /orgs/2/sites/site005\n\ng, managerUser2003, manager001, /orgs/2/sites/site001\ng, managerUser2003, manager001, /orgs/2/sites/site002\ng, managerUser2003, manager001, /orgs/2/sites/site003\ng, managerUser2003, manager001, /orgs/2/sites/site004\ng, managerUser2003, manager001, /orgs/2/sites/site005\n\ng, managerUser2004, manager001, /orgs/2/sites/site001\ng, managerUser2004, manager001, /orgs/2/sites/site002\ng, managerUser2004, manager001, /orgs/2/sites/site003\ng, managerUser2004, manager001, /orgs/2/sites/site004\ng, managerUser2004, manager001, /orgs/2/sites/site005\n\ng, managerUser2005, manager001, /orgs/2/sites/site001\ng, managerUser2005, manager001, /orgs/2/sites/site002\ng, managerUser2005, manager001, /orgs/2/sites/site003\ng, managerUser2005, manager001, /orgs/2/sites/site004\ng, managerUser2005, manager001, /orgs/2/sites/site005\n\ng, managerUser2006, manager001, /orgs/2/sites/site001\ng, managerUser2006, manager001, /orgs/2/sites/site002\ng, managerUser2006, manager001, /orgs/2/sites/site003\ng, managerUser2006, manager001, /orgs/2/sites/site004\ng, managerUser2006, manager001, /orgs/2/sites/site005\n\ng, managerUser2007, manager001, /orgs/2/sites/site001\ng, managerUser2007, manager001, /orgs/2/sites/site002\ng, managerUser2007, manager001, /orgs/2/sites/site003\ng, managerUser2007, manager001, /orgs/2/sites/site004\ng, managerUser2007, manager001, /orgs/2/sites/site005\n\ng, managerUser2008,  manager001, /orgs/2/sites/site001\ng, managerUser2008,  manager001, /orgs/2/sites/site002\ng, managerUser2008,  manager001, /orgs/2/sites/site003\ng, managerUser2008,  manager001, /orgs/2/sites/site004\ng, managerUser2008,  manager001, /orgs/2/sites/site005\n\ng, managerUser2009,  manager001, /orgs/2/sites/site001\ng, managerUser2009,  manager001, /orgs/2/sites/site002\ng, managerUser2009,  manager001, /orgs/2/sites/site003\ng, managerUser2009,  manager001, /orgs/2/sites/site004\ng, managerUser2009,  manager001, /orgs/2/sites/site005\n\ng, managerUser2010,  manager001, /orgs/2/sites/site001\ng, managerUser2010,  manager001, /orgs/2/sites/site002\ng, managerUser2010,  manager001, /orgs/2/sites/site003\ng, managerUser2010,  manager001, /orgs/2/sites/site004\ng, managerUser2010,  manager001, /orgs/2/sites/site005\n\ng, managerUser2011,  manager001, /orgs/2/sites/site001\ng, managerUser2011,  manager001, /orgs/2/sites/site002\ng, managerUser2011,  manager001, /orgs/2/sites/site003\ng, managerUser2011,  manager001, /orgs/2/sites/site004\ng, managerUser2011,  manager001, /orgs/2/sites/site005\n\ng, managerUser2012,  manager001, /orgs/2/sites/site001\ng, managerUser2012,  manager001, /orgs/2/sites/site002\ng, managerUser2012,  manager001, /orgs/2/sites/site003\ng, managerUser2012,  manager001, /orgs/2/sites/site004\ng, managerUser2012,  manager001, /orgs/2/sites/site005\n\ng, managerUser2013, manager001, /orgs/2/sites/site001\ng, managerUser2013, manager001, /orgs/2/sites/site002\ng, managerUser2013, manager001, /orgs/2/sites/site003\ng, managerUser2013, manager001, /orgs/2/sites/site004\ng, managerUser2013, manager001, /orgs/2/sites/site005\n\ng, managerUser2014, manager001, /orgs/2/sites/site001\ng, managerUser2014, manager001, /orgs/2/sites/site002\ng, managerUser2014, manager001, /orgs/2/sites/site003\ng, managerUser2014, manager001, /orgs/2/sites/site004\ng, managerUser2014, manager001, /orgs/2/sites/site005\n\ng, managerUser2015, manager001, /orgs/2/sites/site001\ng, managerUser2015, manager001, /orgs/2/sites/site002\ng, managerUser2015, manager001, /orgs/2/sites/site003\ng, managerUser2015, manager001, /orgs/2/sites/site004\ng, managerUser2015, manager001, /orgs/2/sites/site005\n\ng, managerUser2016, manager001, /orgs/2/sites/site001\ng, managerUser2016, manager001, /orgs/2/sites/site002\ng, managerUser2016, manager001, /orgs/2/sites/site003\ng, managerUser2016, manager001, /orgs/2/sites/site004\ng, managerUser2016, manager001, /orgs/2/sites/site005\n\ng, managerUser2017, manager001, /orgs/2/sites/site001\ng, managerUser2017, manager001, /orgs/2/sites/site002\ng, managerUser2017, manager001, /orgs/2/sites/site003\ng, managerUser2017, manager001, /orgs/2/sites/site004\ng, managerUser2017, manager001, /orgs/2/sites/site005\n\ng, managerUser2018,  manager001, /orgs/2/sites/site001\ng, managerUser2018,  manager001, /orgs/2/sites/site002\ng, managerUser2018,  manager001, /orgs/2/sites/site003\ng, managerUser2018,  manager001, /orgs/2/sites/site004\ng, managerUser2018,  manager001, /orgs/2/sites/site005\n\ng, managerUser2019,  manager001, /orgs/2/sites/site001\ng, managerUser2019,  manager001, /orgs/2/sites/site002\ng, managerUser2019,  manager001, /orgs/2/sites/site003\ng, managerUser2019,  manager001, /orgs/2/sites/site004\ng, managerUser2019,  manager001, /orgs/2/sites/site005\n\ng, managerUser2020,  manager001, /orgs/2/sites/site001\ng, managerUser2020,  manager001, /orgs/2/sites/site002\ng, managerUser2020,  manager001, /orgs/2/sites/site003\ng, managerUser2020,  manager001, /orgs/2/sites/site004\ng, managerUser2020,  manager001, /orgs/2/sites/site005\n\ng, managerUser2021,  manager001, /orgs/2/sites/site001\ng, managerUser2021,  manager001, /orgs/2/sites/site002\ng, managerUser2021,  manager001, /orgs/2/sites/site003\ng, managerUser2021,  manager001, /orgs/2/sites/site004\ng, managerUser2021,  manager001, /orgs/2/sites/site005\n\ng, managerUser2022,  manager001, /orgs/2/sites/site001\ng, managerUser2022,  manager001, /orgs/2/sites/site002\ng, managerUser2022,  manager001, /orgs/2/sites/site003\ng, managerUser2022,  manager001, /orgs/2/sites/site004\ng, managerUser2022,  manager001, /orgs/2/sites/site005\n\ng, managerUser2023, manager001, /orgs/2/sites/site001\ng, managerUser2023, manager001, /orgs/2/sites/site002\ng, managerUser2023, manager001, /orgs/2/sites/site003\ng, managerUser2023, manager001, /orgs/2/sites/site004\ng, managerUser2023, manager001, /orgs/2/sites/site005\n\ng, managerUser2024, manager001, /orgs/2/sites/site001\ng, managerUser2024, manager001, /orgs/2/sites/site002\ng, managerUser2024, manager001, /orgs/2/sites/site003\ng, managerUser2024, manager001, /orgs/2/sites/site004\ng, managerUser2024, manager001, /orgs/2/sites/site005\n\ng, managerUser2025, manager001, /orgs/2/sites/site001\ng, managerUser2025, manager001, /orgs/2/sites/site002\ng, managerUser2025, manager001, /orgs/2/sites/site003\ng, managerUser2025, manager001, /orgs/2/sites/site004\ng, managerUser2025, manager001, /orgs/2/sites/site005\n\ng, managerUser2026, manager001, /orgs/2/sites/site001\ng, managerUser2026, manager001, /orgs/2/sites/site002\ng, managerUser2026, manager001, /orgs/2/sites/site003\ng, managerUser2026, manager001, /orgs/2/sites/site004\ng, managerUser2026, manager001, /orgs/2/sites/site005\n\ng, managerUser2027, manager001, /orgs/2/sites/site001\ng, managerUser2027, manager001, /orgs/2/sites/site002\ng, managerUser2027, manager001, /orgs/2/sites/site003\ng, managerUser2027, manager001, /orgs/2/sites/site004\ng, managerUser2027, manager001, /orgs/2/sites/site005\n\ng, managerUser2028,  manager001, /orgs/2/sites/site001\ng, managerUser2028,  manager001, /orgs/2/sites/site002\ng, managerUser2028,  manager001, /orgs/2/sites/site003\ng, managerUser2028,  manager001, /orgs/2/sites/site004\ng, managerUser2028,  manager001, /orgs/2/sites/site005\n\ng, managerUser2029,  manager001, /orgs/2/sites/site001\ng, managerUser2029,  manager001, /orgs/2/sites/site002\ng, managerUser2029,  manager001, /orgs/2/sites/site003\ng, managerUser2029,  manager001, /orgs/2/sites/site004\ng, managerUser2029,  manager001, /orgs/2/sites/site005\n\ng, managerUser2030,  manager001, /orgs/2/sites/site001\ng, managerUser2030,  manager001, /orgs/2/sites/site002\ng, managerUser2030,  manager001, /orgs/2/sites/site003\ng, managerUser2030,  manager001, /orgs/2/sites/site004\ng, managerUser2030,  manager001, /orgs/2/sites/site005\n\ng, managerUser2031,  manager001, /orgs/2/sites/site001\ng, managerUser2031,  manager001, /orgs/2/sites/site002\ng, managerUser2031,  manager001, /orgs/2/sites/site003\ng, managerUser2031,  manager001, /orgs/2/sites/site004\ng, managerUser2031,  manager001, /orgs/2/sites/site005\n\ng, managerUser2032,  manager001, /orgs/2/sites/site001\ng, managerUser2032,  manager001, /orgs/2/sites/site002\ng, managerUser2032,  manager001, /orgs/2/sites/site003\ng, managerUser2032,  manager001, /orgs/2/sites/site004\ng, managerUser2032,  manager001, /orgs/2/sites/site005\n\ng, managerUser2033, manager001, /orgs/2/sites/site001\ng, managerUser2033, manager001, /orgs/2/sites/site002\ng, managerUser2033, manager001, /orgs/2/sites/site003\ng, managerUser2033, manager001, /orgs/2/sites/site004\ng, managerUser2033, manager001, /orgs/2/sites/site005\n\ng, managerUser2034, manager001, /orgs/2/sites/site001\ng, managerUser2034, manager001, /orgs/2/sites/site002\ng, managerUser2034, manager001, /orgs/2/sites/site003\ng, managerUser2034, manager001, /orgs/2/sites/site004\ng, managerUser2034, manager001, /orgs/2/sites/site005\n\ng, managerUser2035, manager001, /orgs/2/sites/site001\ng, managerUser2035, manager001, /orgs/2/sites/site002\ng, managerUser2035, manager001, /orgs/2/sites/site003\ng, managerUser2035, manager001, /orgs/2/sites/site004\ng, managerUser2035, manager001, /orgs/2/sites/site005\n\ng, managerUser2036, manager001, /orgs/2/sites/site001\ng, managerUser2036, manager001, /orgs/2/sites/site002\ng, managerUser2036, manager001, /orgs/2/sites/site003\ng, managerUser2036, manager001, /orgs/2/sites/site004\ng, managerUser2036, manager001, /orgs/2/sites/site005\n\ng, managerUser2037, manager001, /orgs/2/sites/site001\ng, managerUser2037, manager001, /orgs/2/sites/site002\ng, managerUser2037, manager001, /orgs/2/sites/site003\ng, managerUser2037, manager001, /orgs/2/sites/site004\ng, managerUser2037, manager001, /orgs/2/sites/site005\n\ng, managerUser2038,  manager001, /orgs/2/sites/site001\ng, managerUser2038,  manager001, /orgs/2/sites/site002\ng, managerUser2038,  manager001, /orgs/2/sites/site003\ng, managerUser2038,  manager001, /orgs/2/sites/site004\ng, managerUser2038,  manager001, /orgs/2/sites/site005\n\ng, managerUser2039,  manager001, /orgs/2/sites/site001\ng, managerUser2039,  manager001, /orgs/2/sites/site002\ng, managerUser2039,  manager001, /orgs/2/sites/site003\ng, managerUser2039,  manager001, /orgs/2/sites/site004\ng, managerUser2039,  manager001, /orgs/2/sites/site005\n\ng, managerUser2040,  manager001, /orgs/2/sites/site001\ng, managerUser2040,  manager001, /orgs/2/sites/site002\ng, managerUser2040,  manager001, /orgs/2/sites/site003\ng, managerUser2040,  manager001, /orgs/2/sites/site004\ng, managerUser2040,  manager001, /orgs/2/sites/site005\n\ng, managerUser2041,  manager001, /orgs/2/sites/site001\ng, managerUser2041,  manager001, /orgs/2/sites/site002\ng, managerUser2041,  manager001, /orgs/2/sites/site003\ng, managerUser2041,  manager001, /orgs/2/sites/site004\ng, managerUser2041,  manager001, /orgs/2/sites/site005\n\ng, managerUser2042,  manager001, /orgs/2/sites/site001\ng, managerUser2042,  manager001, /orgs/2/sites/site002\ng, managerUser2042,  manager001, /orgs/2/sites/site003\ng, managerUser2042,  manager001, /orgs/2/sites/site004\ng, managerUser2042,  manager001, /orgs/2/sites/site005\n\ng, managerUser2043, manager001, /orgs/2/sites/site001\ng, managerUser2043, manager001, /orgs/2/sites/site002\ng, managerUser2043, manager001, /orgs/2/sites/site003\ng, managerUser2043, manager001, /orgs/2/sites/site004\ng, managerUser2043, manager001, /orgs/2/sites/site005\n\ng, managerUser2044, manager001, /orgs/2/sites/site001\ng, managerUser2044, manager001, /orgs/2/sites/site002\ng, managerUser2044, manager001, /orgs/2/sites/site003\ng, managerUser2044, manager001, /orgs/2/sites/site004\ng, managerUser2044, manager001, /orgs/2/sites/site005\n\ng, managerUser2045, manager001, /orgs/2/sites/site001\ng, managerUser2045, manager001, /orgs/2/sites/site002\ng, managerUser2045, manager001, /orgs/2/sites/site003\ng, managerUser2045, manager001, /orgs/2/sites/site004\ng, managerUser2045, manager001, /orgs/2/sites/site005\n\ng, managerUser2046, manager001, /orgs/2/sites/site001\ng, managerUser2046, manager001, /orgs/2/sites/site002\ng, managerUser2046, manager001, /orgs/2/sites/site003\ng, managerUser2046, manager001, /orgs/2/sites/site004\ng, managerUser2046, manager001, /orgs/2/sites/site005\n\ng, managerUser2047, manager001, /orgs/2/sites/site001\ng, managerUser2047, manager001, /orgs/2/sites/site002\ng, managerUser2047, manager001, /orgs/2/sites/site003\ng, managerUser2047, manager001, /orgs/2/sites/site004\ng, managerUser2047, manager001, /orgs/2/sites/site005\n\ng, managerUser2048,  manager001, /orgs/2/sites/site001\ng, managerUser2048,  manager001, /orgs/2/sites/site002\ng, managerUser2048,  manager001, /orgs/2/sites/site003\ng, managerUser2048,  manager001, /orgs/2/sites/site004\ng, managerUser2048,  manager001, /orgs/2/sites/site005\n\ng, managerUser2049,  manager001, /orgs/2/sites/site001\ng, managerUser2049,  manager001, /orgs/2/sites/site002\ng, managerUser2049,  manager001, /orgs/2/sites/site003\ng, managerUser2049,  manager001, /orgs/2/sites/site004\ng, managerUser2049,  manager001, /orgs/2/sites/site005\n\ng, managerUser2050,  manager001, /orgs/2/sites/site001\ng, managerUser2050,  manager001, /orgs/2/sites/site002\ng, managerUser2050,  manager001, /orgs/2/sites/site003\ng, managerUser2050,  manager001, /orgs/2/sites/site004\ng, managerUser2050,  manager001, /orgs/2/sites/site005\n\n# Group - customer001, / org2\ng, customerUser1001, customer001, /orgs/2/sites/site001\ng, customerUser1001, customer001, /orgs/2/sites/site002\ng, customerUser1001, customer001, /orgs/2/sites/site003\ng, customerUser1001, customer001, /orgs/2/sites/site004\ng, customerUser1001, customer001, /orgs/2/sites/site005\n\ng, customerUser1001, customer001, /orgs/2/sites/site001\ng, customerUser1001, customer001, /orgs/2/sites/site002\ng, customerUser1001, customer001, /orgs/2/sites/site003\ng, customerUser1001, customer001, /orgs/2/sites/site004\ng, customerUser1001, customer001, /orgs/2/sites/site005\n\ng, customerUser1003, customer001, /orgs/2/sites/site001\ng, customerUser1003, customer001, /orgs/2/sites/site002\ng, customerUser1003, customer001, /orgs/2/sites/site003\ng, customerUser1003, customer001, /orgs/2/sites/site004\ng, customerUser1003, customer001, /orgs/2/sites/site005\n\ng, customerUser1004, customer001, /orgs/2/sites/site001\ng, customerUser1004, customer001, /orgs/2/sites/site002\ng, customerUser1004, customer001, /orgs/2/sites/site003\ng, customerUser1004, customer001, /orgs/2/sites/site004\ng, customerUser1004, customer001, /orgs/2/sites/site005\n\ng, customerUser1005, customer001, /orgs/2/sites/site001\ng, customerUser1005, customer001, /orgs/2/sites/site002\ng, customerUser1005, customer001, /orgs/2/sites/site003\ng, customerUser1005, customer001, /orgs/2/sites/site004\ng, customerUser1005, customer001, /orgs/2/sites/site005\n\ng, customerUser1006, customer001, /orgs/2/sites/site001\ng, customerUser1006, customer001, /orgs/2/sites/site002\ng, customerUser1006, customer001, /orgs/2/sites/site003\ng, customerUser1006, customer001, /orgs/2/sites/site004\ng, customerUser1006, customer001, /orgs/2/sites/site005\n\ng, customerUser1007, customer001, /orgs/2/sites/site001\ng, customerUser1007, customer001, /orgs/2/sites/site002\ng, customerUser1007, customer001, /orgs/2/sites/site003\ng, customerUser1007, customer001, /orgs/2/sites/site004\ng, customerUser1007, customer001, /orgs/2/sites/site005\n\ng, customerUser1008,  customer001, /orgs/2/sites/site001\ng, customerUser1008,  customer001, /orgs/2/sites/site002\ng, customerUser1008,  customer001, /orgs/2/sites/site003\ng, customerUser1008,  customer001, /orgs/2/sites/site004\ng, customerUser1008,  customer001, /orgs/2/sites/site005\n\ng, customerUser1009,  customer001, /orgs/2/sites/site001\ng, customerUser1009,  customer001, /orgs/2/sites/site002\ng, customerUser1009,  customer001, /orgs/2/sites/site003\ng, customerUser1009,  customer001, /orgs/2/sites/site004\ng, customerUser1009,  customer001, /orgs/2/sites/site005\n\ng, customerUser1010,  customer001, /orgs/2/sites/site001\ng, customerUser1010,  customer001, /orgs/2/sites/site002\ng, customerUser1010,  customer001, /orgs/2/sites/site003\ng, customerUser1010,  customer001, /orgs/2/sites/site004\ng, customerUser1010,  customer001, /orgs/2/sites/site005\n\ng, customerUser1011,  customer001, /orgs/2/sites/site001\ng, customerUser1011,  customer001, /orgs/2/sites/site002\ng, customerUser1011,  customer001, /orgs/2/sites/site003\ng, customerUser1011,  customer001, /orgs/2/sites/site004\ng, customerUser1011,  customer001, /orgs/2/sites/site005\n\ng, customerUser1012,  customer001, /orgs/2/sites/site001\ng, customerUser1012,  customer001, /orgs/2/sites/site002\ng, customerUser1012,  customer001, /orgs/2/sites/site003\ng, customerUser1012,  customer001, /orgs/2/sites/site004\ng, customerUser1012,  customer001, /orgs/2/sites/site005\n\ng, customerUser1013, customer001, /orgs/2/sites/site001\ng, customerUser1013, customer001, /orgs/2/sites/site002\ng, customerUser1013, customer001, /orgs/2/sites/site003\ng, customerUser1013, customer001, /orgs/2/sites/site004\ng, customerUser1013, customer001, /orgs/2/sites/site005\n\ng, customerUser1014, customer001, /orgs/2/sites/site001\ng, customerUser1014, customer001, /orgs/2/sites/site002\ng, customerUser1014, customer001, /orgs/2/sites/site003\ng, customerUser1014, customer001, /orgs/2/sites/site004\ng, customerUser1014, customer001, /orgs/2/sites/site005\n\ng, customerUser1015, customer001, /orgs/2/sites/site001\ng, customerUser1015, customer001, /orgs/2/sites/site002\ng, customerUser1015, customer001, /orgs/2/sites/site003\ng, customerUser1015, customer001, /orgs/2/sites/site004\ng, customerUser1015, customer001, /orgs/2/sites/site005\n\ng, customerUser1016, customer001, /orgs/2/sites/site001\ng, customerUser1016, customer001, /orgs/2/sites/site002\ng, customerUser1016, customer001, /orgs/2/sites/site003\ng, customerUser1016, customer001, /orgs/2/sites/site004\ng, customerUser1016, customer001, /orgs/2/sites/site005\n\ng, customerUser1017, customer001, /orgs/2/sites/site001\ng, customerUser1017, customer001, /orgs/2/sites/site002\ng, customerUser1017, customer001, /orgs/2/sites/site003\ng, customerUser1017, customer001, /orgs/2/sites/site004\ng, customerUser1017, customer001, /orgs/2/sites/site005\n\ng, customerUser1018,  customer001, /orgs/2/sites/site001\ng, customerUser1018,  customer001, /orgs/2/sites/site002\ng, customerUser1018,  customer001, /orgs/2/sites/site003\ng, customerUser1018,  customer001, /orgs/2/sites/site004\ng, customerUser1018,  customer001, /orgs/2/sites/site005\n\ng, customerUser1019,  customer001, /orgs/2/sites/site001\ng, customerUser1019,  customer001, /orgs/2/sites/site002\ng, customerUser1019,  customer001, /orgs/2/sites/site003\ng, customerUser1019,  customer001, /orgs/2/sites/site004\ng, customerUser1019,  customer001, /orgs/2/sites/site005\n\ng, customerUser1020,  customer001, /orgs/2/sites/site001\ng, customerUser1020,  customer001, /orgs/2/sites/site002\ng, customerUser1020,  customer001, /orgs/2/sites/site003\ng, customerUser1020,  customer001, /orgs/2/sites/site004\ng, customerUser1020,  customer001, /orgs/2/sites/site005\n\ng, customerUser1021,  customer001, /orgs/2/sites/site001\ng, customerUser1021,  customer001, /orgs/2/sites/site002\ng, customerUser1021,  customer001, /orgs/2/sites/site003\ng, customerUser1021,  customer001, /orgs/2/sites/site004\ng, customerUser1021,  customer001, /orgs/2/sites/site005\n\ng, customerUser1022,  customer001, /orgs/2/sites/site001\ng, customerUser1022,  customer001, /orgs/2/sites/site002\ng, customerUser1022,  customer001, /orgs/2/sites/site003\ng, customerUser1022,  customer001, /orgs/2/sites/site004\ng, customerUser1022,  customer001, /orgs/2/sites/site005\n\ng, customerUser1023, customer001, /orgs/2/sites/site001\ng, customerUser1023, customer001, /orgs/2/sites/site002\ng, customerUser1023, customer001, /orgs/2/sites/site003\ng, customerUser1023, customer001, /orgs/2/sites/site004\ng, customerUser1023, customer001, /orgs/2/sites/site005\n\ng, customerUser1024, customer001, /orgs/2/sites/site001\ng, customerUser1024, customer001, /orgs/2/sites/site002\ng, customerUser1024, customer001, /orgs/2/sites/site003\ng, customerUser1024, customer001, /orgs/2/sites/site004\ng, customerUser1024, customer001, /orgs/2/sites/site005\n\ng, customerUser1025, customer001, /orgs/2/sites/site001\ng, customerUser1025, customer001, /orgs/2/sites/site002\ng, customerUser1025, customer001, /orgs/2/sites/site003\ng, customerUser1025, customer001, /orgs/2/sites/site004\ng, customerUser1025, customer001, /orgs/2/sites/site005\n\ng, customerUser1026, customer001, /orgs/2/sites/site001\ng, customerUser1026, customer001, /orgs/2/sites/site002\ng, customerUser1026, customer001, /orgs/2/sites/site003\ng, customerUser1026, customer001, /orgs/2/sites/site004\ng, customerUser1026, customer001, /orgs/2/sites/site005\n\ng, customerUser1027, customer001, /orgs/2/sites/site001\ng, customerUser1027, customer001, /orgs/2/sites/site002\ng, customerUser1027, customer001, /orgs/2/sites/site003\ng, customerUser1027, customer001, /orgs/2/sites/site004\ng, customerUser1027, customer001, /orgs/2/sites/site005\n\ng, customerUser1028,  customer001, /orgs/2/sites/site001\ng, customerUser1028,  customer001, /orgs/2/sites/site002\ng, customerUser1028,  customer001, /orgs/2/sites/site003\ng, customerUser1028,  customer001, /orgs/2/sites/site004\ng, customerUser1028,  customer001, /orgs/2/sites/site005\n\ng, customerUser1029,  customer001, /orgs/2/sites/site001\ng, customerUser1029,  customer001, /orgs/2/sites/site002\ng, customerUser1029,  customer001, /orgs/2/sites/site003\ng, customerUser1029,  customer001, /orgs/2/sites/site004\ng, customerUser1029,  customer001, /orgs/2/sites/site005\n\ng, customerUser1030,  customer001, /orgs/2/sites/site001\ng, customerUser1030,  customer001, /orgs/2/sites/site002\ng, customerUser1030,  customer001, /orgs/2/sites/site003\ng, customerUser1030,  customer001, /orgs/2/sites/site004\ng, customerUser1030,  customer001, /orgs/2/sites/site005\n\ng, customerUser1031,  customer001, /orgs/2/sites/site001\ng, customerUser1031,  customer001, /orgs/2/sites/site002\ng, customerUser1031,  customer001, /orgs/2/sites/site003\ng, customerUser1031,  customer001, /orgs/2/sites/site004\ng, customerUser1031,  customer001, /orgs/2/sites/site005\n\ng, customerUser1032,  customer001, /orgs/2/sites/site001\ng, customerUser1032,  customer001, /orgs/2/sites/site002\ng, customerUser1032,  customer001, /orgs/2/sites/site003\ng, customerUser1032,  customer001, /orgs/2/sites/site004\ng, customerUser1032,  customer001, /orgs/2/sites/site005\n\ng, customerUser1033, customer001, /orgs/2/sites/site001\ng, customerUser1033, customer001, /orgs/2/sites/site002\ng, customerUser1033, customer001, /orgs/2/sites/site003\ng, customerUser1033, customer001, /orgs/2/sites/site004\ng, customerUser1033, customer001, /orgs/2/sites/site005\n\ng, customerUser1034, customer001, /orgs/2/sites/site001\ng, customerUser1034, customer001, /orgs/2/sites/site002\ng, customerUser1034, customer001, /orgs/2/sites/site003\ng, customerUser1034, customer001, /orgs/2/sites/site004\ng, customerUser1034, customer001, /orgs/2/sites/site005\n\ng, customerUser1035, customer001, /orgs/2/sites/site001\ng, customerUser1035, customer001, /orgs/2/sites/site002\ng, customerUser1035, customer001, /orgs/2/sites/site003\ng, customerUser1035, customer001, /orgs/2/sites/site004\ng, customerUser1035, customer001, /orgs/2/sites/site005\n\ng, customerUser1036, customer001, /orgs/2/sites/site001\ng, customerUser1036, customer001, /orgs/2/sites/site002\ng, customerUser1036, customer001, /orgs/2/sites/site003\ng, customerUser1036, customer001, /orgs/2/sites/site004\ng, customerUser1036, customer001, /orgs/2/sites/site005\n\ng, customerUser1037, customer001, /orgs/2/sites/site001\ng, customerUser1037, customer001, /orgs/2/sites/site002\ng, customerUser1037, customer001, /orgs/2/sites/site003\ng, customerUser1037, customer001, /orgs/2/sites/site004\ng, customerUser1037, customer001, /orgs/2/sites/site005\n\ng, customerUser1038,  customer001, /orgs/2/sites/site001\ng, customerUser1038,  customer001, /orgs/2/sites/site002\ng, customerUser1038,  customer001, /orgs/2/sites/site003\ng, customerUser1038,  customer001, /orgs/2/sites/site004\ng, customerUser1038,  customer001, /orgs/2/sites/site005\n\ng, customerUser1039,  customer001, /orgs/2/sites/site001\ng, customerUser1039,  customer001, /orgs/2/sites/site002\ng, customerUser1039,  customer001, /orgs/2/sites/site003\ng, customerUser1039,  customer001, /orgs/2/sites/site004\ng, customerUser1039,  customer001, /orgs/2/sites/site005\n\ng, customerUser1040,  customer001, /orgs/2/sites/site001\ng, customerUser1040,  customer001, /orgs/2/sites/site002\ng, customerUser1040,  customer001, /orgs/2/sites/site003\ng, customerUser1040,  customer001, /orgs/2/sites/site004\ng, customerUser1040,  customer001, /orgs/2/sites/site005\n\ng, customerUser1041,  customer001, /orgs/2/sites/site001\ng, customerUser1041,  customer001, /orgs/2/sites/site002\ng, customerUser1041,  customer001, /orgs/2/sites/site003\ng, customerUser1041,  customer001, /orgs/2/sites/site004\ng, customerUser1041,  customer001, /orgs/2/sites/site005\n\ng, customerUser1042,  customer001, /orgs/2/sites/site001\ng, customerUser1042,  customer001, /orgs/2/sites/site002\ng, customerUser1042,  customer001, /orgs/2/sites/site003\ng, customerUser1042,  customer001, /orgs/2/sites/site004\ng, customerUser1042,  customer001, /orgs/2/sites/site005\n\ng, customerUser1043, customer001, /orgs/2/sites/site001\ng, customerUser1043, customer001, /orgs/2/sites/site002\ng, customerUser1043, customer001, /orgs/2/sites/site003\ng, customerUser1043, customer001, /orgs/2/sites/site004\ng, customerUser1043, customer001, /orgs/2/sites/site005\n\ng, customerUser1044, customer001, /orgs/2/sites/site001\ng, customerUser1044, customer001, /orgs/2/sites/site002\ng, customerUser1044, customer001, /orgs/2/sites/site003\ng, customerUser1044, customer001, /orgs/2/sites/site004\ng, customerUser1044, customer001, /orgs/2/sites/site005\n\ng, customerUser1045, customer001, /orgs/2/sites/site001\ng, customerUser1045, customer001, /orgs/2/sites/site002\ng, customerUser1045, customer001, /orgs/2/sites/site003\ng, customerUser1045, customer001, /orgs/2/sites/site004\ng, customerUser1045, customer001, /orgs/2/sites/site005\n\ng, customerUser1046, customer001, /orgs/2/sites/site001\ng, customerUser1046, customer001, /orgs/2/sites/site002\ng, customerUser1046, customer001, /orgs/2/sites/site003\ng, customerUser1046, customer001, /orgs/2/sites/site004\ng, customerUser1046, customer001, /orgs/2/sites/site005\n\ng, customerUser1047, customer001, /orgs/2/sites/site001\ng, customerUser1047, customer001, /orgs/2/sites/site002\ng, customerUser1047, customer001, /orgs/2/sites/site003\ng, customerUser1047, customer001, /orgs/2/sites/site004\ng, customerUser1047, customer001, /orgs/2/sites/site005\n\ng, customerUser1048,  customer001, /orgs/2/sites/site001\ng, customerUser1048,  customer001, /orgs/2/sites/site002\ng, customerUser1048,  customer001, /orgs/2/sites/site003\ng, customerUser1048,  customer001, /orgs/2/sites/site004\ng, customerUser1048,  customer001, /orgs/2/sites/site005\n\ng, customerUser1049,  customer001, /orgs/2/sites/site001\ng, customerUser1049,  customer001, /orgs/2/sites/site002\ng, customerUser1049,  customer001, /orgs/2/sites/site003\ng, customerUser1049,  customer001, /orgs/2/sites/site004\ng, customerUser1049,  customer001, /orgs/2/sites/site005\n\ng, customerUser1050,  customer001, /orgs/2/sites/site001\ng, customerUser1050,  customer001, /orgs/2/sites/site002\ng, customerUser1050,  customer001, /orgs/2/sites/site003\ng, customerUser1050,  customer001, /orgs/2/sites/site004\ng, customerUser1050,  customer001, /orgs/2/sites/site005\n\n# Group - customer001, / org2\ng, customerUser2001, customer001, /orgs/2/sites/site001\ng, customerUser2001, customer001, /orgs/2/sites/site002\ng, customerUser2001, customer001, /orgs/2/sites/site003\ng, customerUser2001, customer001, /orgs/2/sites/site004\ng, customerUser2001, customer001, /orgs/2/sites/site005\n\ng, customerUser2001, customer001, /orgs/2/sites/site001\ng, customerUser2001, customer001, /orgs/2/sites/site002\ng, customerUser2001, customer001, /orgs/2/sites/site003\ng, customerUser2001, customer001, /orgs/2/sites/site004\ng, customerUser2001, customer001, /orgs/2/sites/site005\n\ng, customerUser2003, customer001, /orgs/2/sites/site001\ng, customerUser2003, customer001, /orgs/2/sites/site002\ng, customerUser2003, customer001, /orgs/2/sites/site003\ng, customerUser2003, customer001, /orgs/2/sites/site004\ng, customerUser2003, customer001, /orgs/2/sites/site005\n\ng, customerUser2004, customer001, /orgs/2/sites/site001\ng, customerUser2004, customer001, /orgs/2/sites/site002\ng, customerUser2004, customer001, /orgs/2/sites/site003\ng, customerUser2004, customer001, /orgs/2/sites/site004\ng, customerUser2004, customer001, /orgs/2/sites/site005\n\ng, customerUser2005, customer001, /orgs/2/sites/site001\ng, customerUser2005, customer001, /orgs/2/sites/site002\ng, customerUser2005, customer001, /orgs/2/sites/site003\ng, customerUser2005, customer001, /orgs/2/sites/site004\ng, customerUser2005, customer001, /orgs/2/sites/site005\n\ng, customerUser2006, customer001, /orgs/2/sites/site001\ng, customerUser2006, customer001, /orgs/2/sites/site002\ng, customerUser2006, customer001, /orgs/2/sites/site003\ng, customerUser2006, customer001, /orgs/2/sites/site004\ng, customerUser2006, customer001, /orgs/2/sites/site005\n\ng, customerUser2007, customer001, /orgs/2/sites/site001\ng, customerUser2007, customer001, /orgs/2/sites/site002\ng, customerUser2007, customer001, /orgs/2/sites/site003\ng, customerUser2007, customer001, /orgs/2/sites/site004\ng, customerUser2007, customer001, /orgs/2/sites/site005\n\ng, customerUser2008,  customer001, /orgs/2/sites/site001\ng, customerUser2008,  customer001, /orgs/2/sites/site002\ng, customerUser2008,  customer001, /orgs/2/sites/site003\ng, customerUser2008,  customer001, /orgs/2/sites/site004\ng, customerUser2008,  customer001, /orgs/2/sites/site005\n\ng, customerUser2009,  customer001, /orgs/2/sites/site001\ng, customerUser2009,  customer001, /orgs/2/sites/site002\ng, customerUser2009,  customer001, /orgs/2/sites/site003\ng, customerUser2009,  customer001, /orgs/2/sites/site004\ng, customerUser2009,  customer001, /orgs/2/sites/site005\n\ng, customerUser2010,  customer001, /orgs/2/sites/site001\ng, customerUser2010,  customer001, /orgs/2/sites/site002\ng, customerUser2010,  customer001, /orgs/2/sites/site003\ng, customerUser2010,  customer001, /orgs/2/sites/site004\ng, customerUser2010,  customer001, /orgs/2/sites/site005\n\ng, customerUser2011,  customer001, /orgs/2/sites/site001\ng, customerUser2011,  customer001, /orgs/2/sites/site002\ng, customerUser2011,  customer001, /orgs/2/sites/site003\ng, customerUser2011,  customer001, /orgs/2/sites/site004\ng, customerUser2011,  customer001, /orgs/2/sites/site005\n\ng, customerUser2012,  customer001, /orgs/2/sites/site001\ng, customerUser2012,  customer001, /orgs/2/sites/site002\ng, customerUser2012,  customer001, /orgs/2/sites/site003\ng, customerUser2012,  customer001, /orgs/2/sites/site004\ng, customerUser2012,  customer001, /orgs/2/sites/site005\n\ng, customerUser2013, customer001, /orgs/2/sites/site001\ng, customerUser2013, customer001, /orgs/2/sites/site002\ng, customerUser2013, customer001, /orgs/2/sites/site003\ng, customerUser2013, customer001, /orgs/2/sites/site004\ng, customerUser2013, customer001, /orgs/2/sites/site005\n\ng, customerUser2014, customer001, /orgs/2/sites/site001\ng, customerUser2014, customer001, /orgs/2/sites/site002\ng, customerUser2014, customer001, /orgs/2/sites/site003\ng, customerUser2014, customer001, /orgs/2/sites/site004\ng, customerUser2014, customer001, /orgs/2/sites/site005\n\ng, customerUser2015, customer001, /orgs/2/sites/site001\ng, customerUser2015, customer001, /orgs/2/sites/site002\ng, customerUser2015, customer001, /orgs/2/sites/site003\ng, customerUser2015, customer001, /orgs/2/sites/site004\ng, customerUser2015, customer001, /orgs/2/sites/site005\n\ng, customerUser2016, customer001, /orgs/2/sites/site001\ng, customerUser2016, customer001, /orgs/2/sites/site002\ng, customerUser2016, customer001, /orgs/2/sites/site003\ng, customerUser2016, customer001, /orgs/2/sites/site004\ng, customerUser2016, customer001, /orgs/2/sites/site005\n\ng, customerUser2017, customer001, /orgs/2/sites/site001\ng, customerUser2017, customer001, /orgs/2/sites/site002\ng, customerUser2017, customer001, /orgs/2/sites/site003\ng, customerUser2017, customer001, /orgs/2/sites/site004\ng, customerUser2017, customer001, /orgs/2/sites/site005\n\ng, customerUser2018,  customer001, /orgs/2/sites/site001\ng, customerUser2018,  customer001, /orgs/2/sites/site002\ng, customerUser2018,  customer001, /orgs/2/sites/site003\ng, customerUser2018,  customer001, /orgs/2/sites/site004\ng, customerUser2018,  customer001, /orgs/2/sites/site005\n\ng, customerUser2019,  customer001, /orgs/2/sites/site001\ng, customerUser2019,  customer001, /orgs/2/sites/site002\ng, customerUser2019,  customer001, /orgs/2/sites/site003\ng, customerUser2019,  customer001, /orgs/2/sites/site004\ng, customerUser2019,  customer001, /orgs/2/sites/site005\n\ng, customerUser2020,  customer001, /orgs/2/sites/site001\ng, customerUser2020,  customer001, /orgs/2/sites/site002\ng, customerUser2020,  customer001, /orgs/2/sites/site003\ng, customerUser2020,  customer001, /orgs/2/sites/site004\ng, customerUser2020,  customer001, /orgs/2/sites/site005\n\ng, customerUser2021,  customer001, /orgs/2/sites/site001\ng, customerUser2021,  customer001, /orgs/2/sites/site002\ng, customerUser2021,  customer001, /orgs/2/sites/site003\ng, customerUser2021,  customer001, /orgs/2/sites/site004\ng, customerUser2021,  customer001, /orgs/2/sites/site005\n\ng, customerUser2022,  customer001, /orgs/2/sites/site001\ng, customerUser2022,  customer001, /orgs/2/sites/site002\ng, customerUser2022,  customer001, /orgs/2/sites/site003\ng, customerUser2022,  customer001, /orgs/2/sites/site004\ng, customerUser2022,  customer001, /orgs/2/sites/site005\n\ng, customerUser2023, customer001, /orgs/2/sites/site001\ng, customerUser2023, customer001, /orgs/2/sites/site002\ng, customerUser2023, customer001, /orgs/2/sites/site003\ng, customerUser2023, customer001, /orgs/2/sites/site004\ng, customerUser2023, customer001, /orgs/2/sites/site005\n\ng, customerUser2024, customer001, /orgs/2/sites/site001\ng, customerUser2024, customer001, /orgs/2/sites/site002\ng, customerUser2024, customer001, /orgs/2/sites/site003\ng, customerUser2024, customer001, /orgs/2/sites/site004\ng, customerUser2024, customer001, /orgs/2/sites/site005\n\ng, customerUser2025, customer001, /orgs/2/sites/site001\ng, customerUser2025, customer001, /orgs/2/sites/site002\ng, customerUser2025, customer001, /orgs/2/sites/site003\ng, customerUser2025, customer001, /orgs/2/sites/site004\ng, customerUser2025, customer001, /orgs/2/sites/site005\n\ng, customerUser2026, customer001, /orgs/2/sites/site001\ng, customerUser2026, customer001, /orgs/2/sites/site002\ng, customerUser2026, customer001, /orgs/2/sites/site003\ng, customerUser2026, customer001, /orgs/2/sites/site004\ng, customerUser2026, customer001, /orgs/2/sites/site005\n\ng, customerUser2027, customer001, /orgs/2/sites/site001\ng, customerUser2027, customer001, /orgs/2/sites/site002\ng, customerUser2027, customer001, /orgs/2/sites/site003\ng, customerUser2027, customer001, /orgs/2/sites/site004\ng, customerUser2027, customer001, /orgs/2/sites/site005\n\ng, customerUser2028,  customer001, /orgs/2/sites/site001\ng, customerUser2028,  customer001, /orgs/2/sites/site002\ng, customerUser2028,  customer001, /orgs/2/sites/site003\ng, customerUser2028,  customer001, /orgs/2/sites/site004\ng, customerUser2028,  customer001, /orgs/2/sites/site005\n\ng, customerUser2029,  customer001, /orgs/2/sites/site001\ng, customerUser2029,  customer001, /orgs/2/sites/site002\ng, customerUser2029,  customer001, /orgs/2/sites/site003\ng, customerUser2029,  customer001, /orgs/2/sites/site004\ng, customerUser2029,  customer001, /orgs/2/sites/site005\n\ng, customerUser2030,  customer001, /orgs/2/sites/site001\ng, customerUser2030,  customer001, /orgs/2/sites/site002\ng, customerUser2030,  customer001, /orgs/2/sites/site003\ng, customerUser2030,  customer001, /orgs/2/sites/site004\ng, customerUser2030,  customer001, /orgs/2/sites/site005\n\ng, customerUser2031,  customer001, /orgs/2/sites/site001\ng, customerUser2031,  customer001, /orgs/2/sites/site002\ng, customerUser2031,  customer001, /orgs/2/sites/site003\ng, customerUser2031,  customer001, /orgs/2/sites/site004\ng, customerUser2031,  customer001, /orgs/2/sites/site005\n\ng, customerUser2032,  customer001, /orgs/2/sites/site001\ng, customerUser2032,  customer001, /orgs/2/sites/site002\ng, customerUser2032,  customer001, /orgs/2/sites/site003\ng, customerUser2032,  customer001, /orgs/2/sites/site004\ng, customerUser2032,  customer001, /orgs/2/sites/site005\n\ng, customerUser2033, customer001, /orgs/2/sites/site001\ng, customerUser2033, customer001, /orgs/2/sites/site002\ng, customerUser2033, customer001, /orgs/2/sites/site003\ng, customerUser2033, customer001, /orgs/2/sites/site004\ng, customerUser2033, customer001, /orgs/2/sites/site005\n\ng, customerUser2034, customer001, /orgs/2/sites/site001\ng, customerUser2034, customer001, /orgs/2/sites/site002\ng, customerUser2034, customer001, /orgs/2/sites/site003\ng, customerUser2034, customer001, /orgs/2/sites/site004\ng, customerUser2034, customer001, /orgs/2/sites/site005\n\ng, customerUser2035, customer001, /orgs/2/sites/site001\ng, customerUser2035, customer001, /orgs/2/sites/site002\ng, customerUser2035, customer001, /orgs/2/sites/site003\ng, customerUser2035, customer001, /orgs/2/sites/site004\ng, customerUser2035, customer001, /orgs/2/sites/site005\n\ng, customerUser2036, customer001, /orgs/2/sites/site001\ng, customerUser2036, customer001, /orgs/2/sites/site002\ng, customerUser2036, customer001, /orgs/2/sites/site003\ng, customerUser2036, customer001, /orgs/2/sites/site004\ng, customerUser2036, customer001, /orgs/2/sites/site005\n\ng, customerUser2037, customer001, /orgs/2/sites/site001\ng, customerUser2037, customer001, /orgs/2/sites/site002\ng, customerUser2037, customer001, /orgs/2/sites/site003\ng, customerUser2037, customer001, /orgs/2/sites/site004\ng, customerUser2037, customer001, /orgs/2/sites/site005\n\ng, customerUser2038,  customer001, /orgs/2/sites/site001\ng, customerUser2038,  customer001, /orgs/2/sites/site002\ng, customerUser2038,  customer001, /orgs/2/sites/site003\ng, customerUser2038,  customer001, /orgs/2/sites/site004\ng, customerUser2038,  customer001, /orgs/2/sites/site005\n\ng, customerUser2039,  customer001, /orgs/2/sites/site001\ng, customerUser2039,  customer001, /orgs/2/sites/site002\ng, customerUser2039,  customer001, /orgs/2/sites/site003\ng, customerUser2039,  customer001, /orgs/2/sites/site004\ng, customerUser2039,  customer001, /orgs/2/sites/site005\n\ng, customerUser2040,  customer001, /orgs/2/sites/site001\ng, customerUser2040,  customer001, /orgs/2/sites/site002\ng, customerUser2040,  customer001, /orgs/2/sites/site003\ng, customerUser2040,  customer001, /orgs/2/sites/site004\ng, customerUser2040,  customer001, /orgs/2/sites/site005\n\ng, customerUser2041,  customer001, /orgs/2/sites/site001\ng, customerUser2041,  customer001, /orgs/2/sites/site002\ng, customerUser2041,  customer001, /orgs/2/sites/site003\ng, customerUser2041,  customer001, /orgs/2/sites/site004\ng, customerUser2041,  customer001, /orgs/2/sites/site005\n\ng, customerUser2042,  customer001, /orgs/2/sites/site001\ng, customerUser2042,  customer001, /orgs/2/sites/site002\ng, customerUser2042,  customer001, /orgs/2/sites/site003\ng, customerUser2042,  customer001, /orgs/2/sites/site004\ng, customerUser2042,  customer001, /orgs/2/sites/site005\n\ng, customerUser2043, customer001, /orgs/2/sites/site001\ng, customerUser2043, customer001, /orgs/2/sites/site002\ng, customerUser2043, customer001, /orgs/2/sites/site003\ng, customerUser2043, customer001, /orgs/2/sites/site004\ng, customerUser2043, customer001, /orgs/2/sites/site005\n\ng, customerUser2044, customer001, /orgs/2/sites/site001\ng, customerUser2044, customer001, /orgs/2/sites/site002\ng, customerUser2044, customer001, /orgs/2/sites/site003\ng, customerUser2044, customer001, /orgs/2/sites/site004\ng, customerUser2044, customer001, /orgs/2/sites/site005\n\ng, customerUser2045, customer001, /orgs/2/sites/site001\ng, customerUser2045, customer001, /orgs/2/sites/site002\ng, customerUser2045, customer001, /orgs/2/sites/site003\ng, customerUser2045, customer001, /orgs/2/sites/site004\ng, customerUser2045, customer001, /orgs/2/sites/site005\n\ng, customerUser2046, customer001, /orgs/2/sites/site001\ng, customerUser2046, customer001, /orgs/2/sites/site002\ng, customerUser2046, customer001, /orgs/2/sites/site003\ng, customerUser2046, customer001, /orgs/2/sites/site004\ng, customerUser2046, customer001, /orgs/2/sites/site005\n\ng, customerUser2047, customer001, /orgs/2/sites/site001\ng, customerUser2047, customer001, /orgs/2/sites/site002\ng, customerUser2047, customer001, /orgs/2/sites/site003\ng, customerUser2047, customer001, /orgs/2/sites/site004\ng, customerUser2047, customer001, /orgs/2/sites/site005\n\ng, customerUser2048,  customer001, /orgs/2/sites/site001\ng, customerUser2048,  customer001, /orgs/2/sites/site002\ng, customerUser2048,  customer001, /orgs/2/sites/site003\ng, customerUser2048,  customer001, /orgs/2/sites/site004\ng, customerUser2048,  customer001, /orgs/2/sites/site005\n\ng, customerUser2049,  customer001, /orgs/2/sites/site001\ng, customerUser2049,  customer001, /orgs/2/sites/site002\ng, customerUser2049,  customer001, /orgs/2/sites/site003\ng, customerUser2049,  customer001, /orgs/2/sites/site004\ng, customerUser2049,  customer001, /orgs/2/sites/site005\n\ng, customerUser2050,  customer001, /orgs/2/sites/site001\ng, customerUser2050,  customer001, /orgs/2/sites/site002\ng, customerUser2050,  customer001, /orgs/2/sites/site003\ng, customerUser2050,  customer001, /orgs/2/sites/site004\ng, customerUser2050,  customer001, /orgs/2/sites/site005"
  },
  {
    "path": "examples/priority_indeterminate_policy.csv",
    "content": "p, alice, data1, read, indeterminate"
  },
  {
    "path": "examples/priority_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = priority(p.eft) || deny\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/priority_model_enforce_context.conf",
    "content": "[request_definition]\nr = sub, obj, act\nr2 = sub, obj\n\n[policy_definition]\np = sub, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = priority(p.eft) || deny\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\nm2 = g(r2.sub, p.sub)\n"
  },
  {
    "path": "examples/priority_model_explicit.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = priority, sub, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = priority(p.eft) || deny\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/priority_model_explicit_customized.conf",
    "content": "[request_definition]\nr = subject, obj, act\n\n[policy_definition]\np = customized_priority, obj, act, eft, subject\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = priority(p.eft) || deny\n\n[matchers]\nm = g(r.subject, p.subject) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/priority_policy.csv",
    "content": "p, alice, data1, read, allow\np, data1_deny_group, data1, read, deny\np, data1_deny_group, data1, write, deny\np, alice, data1, write, allow\n\ng, alice, data1_deny_group\n\np, data2_allow_group, data2, read, allow\np, bob, data2, read, deny\np, bob, data2, write, deny\n\ng, bob, data2_allow_group"
  },
  {
    "path": "examples/priority_policy_enforce_context.csv",
    "content": "p, alice, data1, read, allow\np, data1_deny_group, data1, read, deny\np, data1_deny_group, data1, write, deny\np, alice, data1, write, allow\n\ng, alice, data1_deny_group\n\np, data2_allow_group, data2, read, allow\np, bob, data2, read, deny\np, bob, data2, write, deny\n\ng, bob, data2_allow_group\n"
  },
  {
    "path": "examples/priority_policy_explicit.csv",
    "content": "p, 10, data1_deny_group, data1, read, deny\np, 10, data1_deny_group, data1, write, deny\np, 10, data2_allow_group, data2, read, allow\np, 10, data2_allow_group, data2, write, allow\n\n\np, 1, alice, data1, write, allow\np, 1, alice, data1, read, allow\np, 1, bob, data2, read, deny\n\ng, bob, data2_allow_group\ng, alice, data1_deny_group\n"
  },
  {
    "path": "examples/priority_policy_explicit_customized.csv",
    "content": "p, 10, data1, read, deny, data1_deny_group\np, 10, data1, write, deny, data1_deny_group\np, 10, data2, read, allow, data2_allow_group\np, 10, data2, write, allow, data2_allow_group\n\n\np, 1, data1, write, allow, alice\np, 1, data1, read, allow, alice\np, 1, data2, read, deny, bob\n\ng, bob, data2_allow_group\ng, alice, data1_deny_group\n"
  },
  {
    "path": "examples/rbac_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_model_in_multi_line.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj \\\n && r.act == p.act"
  },
  {
    "path": "examples/rbac_model_matcher_using_in_op.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')"
  },
  {
    "path": "examples/rbac_model_matcher_using_in_op_bracket.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3']"
  },
  {
    "path": "examples/rbac_policy.csv",
    "content": "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin"
  },
  {
    "path": "examples/rbac_with_all_pattern_model.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && g(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act "
  },
  {
    "path": "examples/rbac_with_all_pattern_policy.csv",
    "content": "p, alice, domain1, book_group, read\np, alice, domain2, book_group, write\n\ng, /book/:id, book_group, *"
  },
  {
    "path": "examples/rbac_with_constraints_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[constraint_definition]\nc = sod(\"finance_requester\", \"finance_approver\")\nc2 = sodMax([\"payroll_view\", \"payroll_edit\", \"payroll_approve\"], 1)\nc3 = roleMax(\"superadmin\", 2)\nc4 = rolePre(\"db_admin\", \"security_trained\")\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n"
  },
  {
    "path": "examples/rbac_with_cycle_policy.csv",
    "content": "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin\ng, data2_admin, super_admin\ng, super_admin, alice\n"
  },
  {
    "path": "examples/rbac_with_deny_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow)) && !some(where (p.eft == deny))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_deny_policy.csv",
    "content": "p, alice, data1, read, allow\np, bob, data2, write, allow\np, data2_admin, data2, read, allow\np, data2_admin, data2, write, allow\np, alice, data2, write, deny\n\ng, alice, data2_admin"
  },
  {
    "path": "examples/rbac_with_different_types_of_roles_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _, (_, _)\ng2 = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, p.dom) && g2(r.obj, p.dom) && regexMatch(r.act, p.act)\n"
  },
  {
    "path": "examples/rbac_with_different_types_of_roles_policy.csv",
    "content": "p, role:owner, domain1, _, (read|write)\np, role:developer, domain1, _, read\n\np, role:owner, domain2, _, (read|write)\np, role:developer, domain2, _, read\n\ng, alice, role:owner, domain1, _, _\ng, bob, role:developer, domain2, _, 9999-12-30 00:00:00\ng, carol, role:owner, domain2, _, 0000-01-02 00:00:00\n\ng2, data1, domain1\ng2, data2, domain2\n"
  },
  {
    "path": "examples/rbac_with_domain_pattern_model.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act "
  },
  {
    "path": "examples/rbac_with_domain_pattern_policy.csv",
    "content": "p, admin, domain1, data1, read\np, admin, domain1, data1, write\np, admin, domain2, data2, read\np, admin, domain2, data2, write\np, admin, *, data3, read\n\ng, alice, admin, *\ng, bob, admin, domain2"
  },
  {
    "path": "examples/rbac_with_domain_temporal_roles_model.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _, (_, _)\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_domain_temporal_roles_policy.csv",
    "content": "p, alice, domain1, data1, read\np, alice, domain1, data1, write\np, data2_admin, domain2, data2, read\np, data2_admin, domain2, data2, write\np, data3_admin, domain3, data3, read\np, data3_admin, domain3, data3, write\np, data4_admin, domain4, data4, read\np, data4_admin, domain4, data4, write\np, data5_admin, domain5, data5, read\np, data5_admin, domain5, data5, write\np, data6_admin, domain6, data6, read\np, data6_admin, domain6, data6, write\np, data7_admin, domain7, data7, read\np, data7_admin, domain7, data7, write\np, data8_admin, domain8, data8, read\np, data8_admin, domain8, data8, write\n\ng, alice, data2_admin, domain2, 0000-01-01 00:00:00, 0000-01-02 00:00:00\ng, alice, data3_admin, domain3, 0000-01-01 00:00:00, 9999-12-30 00:00:00\ng, alice, data4_admin, domain4, _, _\ng, alice, data5_admin, domain5, _, 9999-12-30 00:00:00\ng, alice, data6_admin, domain6, _, 0000-01-02 00:00:00\ng, alice, data7_admin, domain7, 0000-01-01 00:00:00, _\ng, alice, data8_admin, domain8, 9999-12-30 00:00:00, _"
  },
  {
    "path": "examples/rbac_with_domains_conditional_model.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _, (_, _)\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && \\\n(keyMatch(r.act, p.act) || keyMatch2(r.act, p.act) || keyMatch3(r.act, p.act) || keyMatch4(r.act, p.act) || keyMatch5(r.act, p.act) || globMatch(r.act, p.act))\n"
  },
  {
    "path": "examples/rbac_with_domains_conditional_policy.csv",
    "content": "p, test1, domain1, service1, /list\np, test1, domain1, service1, /get/:id/*\np, test1, domain1, service1, /add\np, test1, domain1, service1, /user/*\np, admin, domain1, service1, /*\np, qa1, domain2, service2, /broadcast\np, qa1, domain2, service2, /trip\np, qa1, domain2, service2, /notify\np, qa1, domain2, service2, /dynamic-sql\n\ng, alice, test1, domain1, _, 2999-12-30 00:00:00\ng, bob, qa1, domain2, _, 2999-12-30 00:00:00\ng, jack, test1, domain1, _, 0000-12-30 00:00:00"
  },
  {
    "path": "examples/rbac_with_domains_model.conf",
    "content": "[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_domains_policy.csv",
    "content": "p, admin, domain1, data1, read\np, admin, domain1, data1, write\np, admin, domain2, data2, read\np, admin, domain2, data2, write\ng, alice, admin, domain1\ng, bob, admin, domain2"
  },
  {
    "path": "examples/rbac_with_domains_policy2.csv",
    "content": "p, admin, domain1, data1, read\np, admin, domain1, data1, write\np, admin, domain2, data2, read\np, admin, domain2, data2, write\np, user, domain3, data2, read\ng, alice, admin, domain1\ng, alice, admin, domain2\ng, bob, admin, domain2\ng, bob, user, domain3\n"
  },
  {
    "path": "examples/rbac_with_hierarchy_policy.csv",
    "content": "p, alice, data1, read\np, bob, data2, write\np, data1_admin, data1, read\np, data1_admin, data1, write\np, data2_admin, data2, read\np, data2_admin, data2, write\n\ng, alice, admin\ng, admin, data1_admin\ng, admin, data2_admin"
  },
  {
    "path": "examples/rbac_with_hierarchy_with_domains_policy.csv",
    "content": "p, role:reader, domain1, data1, read\np, role:writer, domain1, data1, write\n\np, alice, domain1, data2, read\np, alice, domain2, data2, read\n\ng, role:global_admin, role:reader, domain1\ng, role:global_admin, role:writer, domain1\n\ng, alice, role:global_admin, domain1\n\n"
  },
  {
    "path": "examples/rbac_with_multiple_policy_model.conf",
    "content": "[request_definition]\nr = user, thing, action\n\n[policy_definition]\np = role, thing, action\np2 = role, action\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.user, p.role) && r.thing == p.thing && r.action == p.action\nm2 = g(r.user, p2.role) && r.action == p.action\n\n[role_definition]\ng = _,_\ng2 = _,_"
  },
  {
    "path": "examples/rbac_with_multiple_policy_policy.csv",
    "content": "p, user, /data, GET\np, admin, /data, POST\n\np2, user, view\np2, admin, create\n\ng, admin, user\ng, alice, admin\ng2, alice, user"
  },
  {
    "path": "examples/rbac_with_not_deny_model.conf",
    "content": "[request_definition]\r\nr = sub, obj, act\r\n\r\n[policy_definition]\r\np = sub, obj, act, eft\r\n\r\n[role_definition]\r\ng = _, _\r\n\r\n[policy_effect]\r\ne = !some(where (p.eft == deny))\r\n\r\n[matchers]\r\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_pattern_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\ng2 = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)"
  },
  {
    "path": "examples/rbac_with_pattern_policy.csv",
    "content": "p, alice, /pen/1, GET\np, alice, /pen2/1, GET\np, book_admin, book_group, GET\np, pen_admin, pen_group, GET\np, *, pen3_group, GET\n\np, /book/admin/:id, pen4_group, GET\ng, /book/user/:id, /book/admin/1\n\np, /book/leader/2, pen4_group, POST\ng, /book/user/:id, /book/leader/2\n\ng, alice, book_admin\ng, bob, pen_admin\n\ng, cathy, /book/1/2/3/4/5\ng, cathy, pen_admin\n\ng2, /book/*, book_group\n\ng2, /book/:id, book_group\ng2, /pen/:id, pen_group\n\ng2, /book2/{id}, book_group\ng2, /pen2/{id}, pen_group\n\ng2, /pen3/:id, pen3_group\ng2, /pen4/:id, pen4_group"
  },
  {
    "path": "examples/rbac_with_resource_roles_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\ng2 = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_resource_roles_policy.csv",
    "content": "p, alice, data1, read\np, bob, data2, write\np, data_group_admin, data_group, write\n\ng, alice, data_group_admin\ng2, data1, data_group\ng2, data2, data_group"
  },
  {
    "path": "examples/rbac_with_temporal_roles_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _, (_, _)\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/rbac_with_temporal_roles_policy.csv",
    "content": "p, alice, data1, read\np, alice, data1, write\np, data2_admin, data2, read\np, data2_admin, data2, write\np, data3_admin, data3, read\np, data3_admin, data3, write\np, data4_admin, data4, read\np, data4_admin, data4, write\np, data5_admin, data5, read\np, data5_admin, data5, write\np, data6_admin, data6, read\np, data6_admin, data6, write\np, data7_admin, data7, read\np, data7_admin, data7, write\np, data8_admin, data8, read\np, data8_admin, data8, write\n\ng, alice, data2_admin, 0000-01-01 00:00:00, 0000-01-02 00:00:00\ng, alice, data3_admin, 0000-01-01 00:00:00, 9999-12-30 00:00:00\ng, alice, data4_admin, _, _\ng, alice, data5_admin, _, 9999-12-30 00:00:00\ng, alice, data6_admin, _, 0000-01-02 00:00:00\ng, alice, data7_admin, 0000-01-01 00:00:00, _\ng, alice, data8_admin, 9999-12-30 00:00:00, _"
  },
  {
    "path": "examples/rebac_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = role, obj_type, act\n\n[role_definition]\ng = _, _, _\ng2 = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, r.obj, p.role) && g2(r.obj, p.obj_type) && r.act == p.act"
  },
  {
    "path": "examples/rebac_policy.csv",
    "content": "p, collaborator, doc, read\n\ng, alice, doc1, collaborator\ng, bob, doc2, collaborator\n\ng2, doc1, doc\ng2, doc2, doc"
  },
  {
    "path": "examples/subject_priority_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act, eft\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = subjectPriority(p.eft) || deny\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/subject_priority_model_with_domain.conf",
    "content": "[request_definition]\nr = sub, obj, dom, act\n\n[policy_definition]\np = sub, obj, dom, act, eft     #sub can't change position,must be first\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = subjectPriority(p.eft) || deny\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act"
  },
  {
    "path": "examples/subject_priority_policy.csv",
    "content": "p, root, data1, read, deny\np, admin, data1, read, deny\n\np, editor, data1, read, deny\np, subscriber, data1, read, deny\n\np, jane, data1, read, allow\np, alice, data1, read, allow\n\ng, admin, root\n\ng, editor, admin\ng, subscriber, admin\n\ng, jane, editor\ng, alice, subscriber"
  },
  {
    "path": "examples/subject_priority_policy_with_domain.csv",
    "content": "p, admin, data1, domain1, write, deny\np, alice, data1, domain1, write, allow\np, admin, data2, domain2, write, deny\np, bob, data2, domain2, write, allow\n\ng, alice, admin, domain1\ng, bob, admin, domain2"
  },
  {
    "path": "examples/syntax_matcher_model.conf",
    "content": "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = some(where (p_eft == allow))\n\n[matchers]\nm = r.sub == \"a.p.p.l.e\"\n"
  },
  {
    "path": "filter_test.go",
    "content": "// Copyright 2018 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc TestInitFilteredAdapter(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_domains_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\t// policy should not be loaded yet\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, false)\n}\n\nfunc TestLoadFilteredPolicy(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_domains_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\tif err := e.LoadPolicy(); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadPolicy: %v\", err)\n\t}\n\n\t// validate initial conditions\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain2\", \"data2\", \"read\"}, true)\n\n\tif err := e.LoadFilteredPolicy(&fileadapter.Filter{\n\t\tP: []string{\"\", \"domain1\"},\n\t\tG: []string{\"\", \"\", \"domain1\"},\n\t}); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\tif !e.IsFiltered() {\n\t\tt.Errorf(\"adapter did not set the filtered flag correctly\")\n\t}\n\n\t// only policies for domain1 should be loaded\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain2\", \"data2\", \"read\"}, false)\n\n\tif err := e.SavePolicy(); err == nil {\n\t\tt.Errorf(\"enforcer did not prevent saving filtered policy\")\n\t}\n\tif err := e.GetAdapter().SavePolicy(e.GetModel()); err == nil {\n\t\tt.Errorf(\"adapter did not prevent saving filtered policy\")\n\t}\n}\n\nfunc TestLoadMoreTypeFilteredPolicy(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_pattern_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_pattern_model.conf\", adapter)\n\tif err := e.LoadPolicy(); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadPolicy: %v\", err)\n\t}\n\te.AddNamedMatchingFunc(\"g2\", \"matching func\", util.KeyMatch2)\n\t_ = e.BuildRoleLinks()\n\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\n\t// validate initial conditions\n\ttestHasPolicy(t, e, []string{\"book_admin\", \"book_group\", \"GET\"}, true)\n\ttestHasPolicy(t, e, []string{\"pen_admin\", \"pen_group\", \"GET\"}, true)\n\n\tif err := e.LoadFilteredPolicy(&fileadapter.Filter{\n\t\tP:  []string{\"book_admin\"},\n\t\tG:  []string{\"alice\"},\n\t\tG2: []string{\"\", \"book_group\"},\n\t}); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\tif !e.IsFiltered() {\n\t\tt.Errorf(\"adapter did not set the filtered flag correctly\")\n\t}\n\n\ttestHasPolicy(t, e, []string{\"alice\", \"/pen/1\", \"GET\"}, false)\n\ttestHasPolicy(t, e, []string{\"alice\", \"/pen2/1\", \"GET\"}, false)\n\ttestHasPolicy(t, e, []string{\"pen_admin\", \"pen_group\", \"GET\"}, false)\n\ttestHasGroupingPolicy(t, e, []string{\"alice\", \"book_admin\"}, true)\n\ttestHasGroupingPolicy(t, e, []string{\"bob\", \"pen_admin\"}, false)\n\ttestHasGroupingPolicy(t, e, []string{\"cathy\", \"pen_admin\"}, false)\n\ttestHasGroupingPolicy(t, e, []string{\"cathy\", \"/book/1/2/3/4/5\"}, false)\n\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/pen/1\", \"GET\", false)\n}\n\nfunc TestAppendFilteredPolicy(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_domains_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\tif err := e.LoadPolicy(); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadPolicy: %v\", err)\n\t}\n\n\t// validate initial conditions\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain2\", \"data2\", \"read\"}, true)\n\n\tif err := e.LoadFilteredPolicy(&fileadapter.Filter{\n\t\tP: []string{\"\", \"domain1\"},\n\t\tG: []string{\"\", \"\", \"domain1\"},\n\t}); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\tif !e.IsFiltered() {\n\t\tt.Errorf(\"adapter did not set the filtered flag correctly\")\n\t}\n\n\t// only policies for domain1 should be loaded\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain2\", \"data2\", \"read\"}, false)\n\n\t// disable clear policy and load second domain\n\tif err := e.LoadIncrementalFilteredPolicy(&fileadapter.Filter{\n\t\tP: []string{\"\", \"domain2\"},\n\t\tG: []string{\"\", \"\", \"domain2\"},\n\t}); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\n\t// both domain policies should be loaded\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain1\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"admin\", \"domain2\", \"data2\", \"read\"}, true)\n}\n\nfunc TestFilteredPolicyInvalidFilter(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_domains_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\tif err := e.LoadFilteredPolicy([]string{\"\", \"domain1\"}); err == nil {\n\t\tt.Errorf(\"expected error in LoadFilteredPolicy, but got nil\")\n\t}\n}\n\nfunc TestFilteredPolicyEmptyFilter(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/rbac_with_domains_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\tif err := e.LoadFilteredPolicy(nil); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n\tif e.IsFiltered() {\n\t\tt.Errorf(\"adapter did not reset the filtered flag correctly\")\n\t}\n\tif err := e.SavePolicy(); err != nil {\n\t\tt.Errorf(\"unexpected error in SavePolicy: %v\", err)\n\t}\n}\n\nfunc TestUnsupportedFilteredPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\terr := e.LoadFilteredPolicy(&fileadapter.Filter{\n\t\tP: []string{\"\", \"domain1\"},\n\t\tG: []string{\"\", \"\", \"domain1\"},\n\t})\n\tif err == nil {\n\t\tt.Errorf(\"encorcer should have reported incompatibility error\")\n\t}\n}\n\nfunc TestFilteredAdapterEmptyFilepath(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\tif err := e.LoadFilteredPolicy(nil); err != nil {\n\t\tt.Errorf(\"unexpected error in LoadFilteredPolicy: %v\", err)\n\t}\n}\n\nfunc TestFilteredAdapterInvalidFilepath(t *testing.T) {\n\te, _ := NewEnforcer()\n\n\tadapter := fileadapter.NewFilteredAdapter(\"examples/does_not_exist_policy.csv\")\n\t_ = e.InitWithAdapter(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\tif err := e.LoadFilteredPolicy(nil); err == nil {\n\t\tt.Errorf(\"expected error in LoadFilteredPolicy, but got nil\")\n\t}\n}\n"
  },
  {
    "path": "frontend.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n)\n\nfunc CasbinJsGetPermissionForUser(e IEnforcer, user string) (string, error) {\n\tmodel := e.GetModel()\n\tm := map[string]interface{}{}\n\n\tm[\"m\"] = model.ToText()\n\n\tpRules := [][]string{}\n\tfor ptype := range model[\"p\"] {\n\t\tpolicies, err := model.GetPolicy(\"p\", ptype)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfor _, rules := range policies {\n\t\t\tpRules = append(pRules, append([]string{ptype}, rules...))\n\t\t}\n\t}\n\tm[\"p\"] = pRules\n\n\tgRules := [][]string{}\n\tfor ptype := range model[\"g\"] {\n\t\tpolicies, err := model.GetPolicy(\"g\", ptype)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfor _, rules := range policies {\n\t\t\tgRules = append(gRules, append([]string{ptype}, rules...))\n\t\t}\n\t}\n\tm[\"g\"] = gRules\n\n\tresult := bytes.NewBuffer([]byte{})\n\tencoder := json.NewEncoder(result)\n\tencoder.SetEscapeHTML(false)\n\terr := encoder.Encode(m)\n\treturn result.String(), err\n}\n"
  },
  {
    "path": "frontend_old.go",
    "content": "// Copyright 2021 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport \"encoding/json\"\n\nfunc CasbinJsGetPermissionForUserOld(e IEnforcer, user string) ([]byte, error) {\n\tpolicy, err := e.GetImplicitPermissionsForUser(user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpermission := make(map[string][]string)\n\tfor i := 0; i < len(policy); i++ {\n\t\tpermission[policy[i][2]] = append(permission[policy[i][2]], policy[i][1])\n\t}\n\tb, _ := json.Marshal(permission)\n\treturn b, nil\n}\n"
  },
  {
    "path": "frontend_old_test.go",
    "content": "// Copyright 2021 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\nfunc contains(arr []string, target string) bool {\n\tfor _, item := range arr {\n\t\tif item == target {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc TestCasbinJsGetPermissionForUserOld(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\ttargetStr, _ := CasbinJsGetPermissionForUserOld(e, \"alice\")\n\tt.Log(\"GetPermissionForUser Alice\", string(targetStr))\n\taliceTarget := make(map[string][]string)\n\terr = json.Unmarshal(targetStr, &aliceTarget)\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\tperm, ok := aliceTarget[\"read\"]\n\tif !ok {\n\t\tt.Errorf(\"Test error: Alice doesn't have read permission\")\n\t}\n\tif !contains(perm, \"data1\") {\n\t\tt.Errorf(\"Test error: Alice cannot read data1\")\n\t}\n\tif !contains(perm, \"data2\") {\n\t\tt.Errorf(\"Test error: Alice cannot read data2\")\n\t}\n\tperm, ok = aliceTarget[\"write\"]\n\tif !ok {\n\t\tt.Errorf(\"Test error: Alice doesn't have write permission\")\n\t}\n\tif contains(perm, \"data1\") {\n\t\tt.Errorf(\"Test error: Alice can write data1\")\n\t}\n\tif !contains(perm, \"data2\") {\n\t\tt.Errorf(\"Test error: Alice cannot write data2\")\n\t}\n\n\ttargetStr, _ = CasbinJsGetPermissionForUserOld(e, \"bob\")\n\tt.Log(\"GetPermissionForUser Bob\", string(targetStr))\n\tbobTarget := make(map[string][]string)\n\terr = json.Unmarshal(targetStr, &bobTarget)\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\t_, ok = bobTarget[\"read\"]\n\tif ok {\n\t\tt.Errorf(\"Test error: Bob has read permission\")\n\t}\n\tperm, ok = bobTarget[\"write\"]\n\tif !ok {\n\t\tt.Errorf(\"Test error: Bob doesn't have permission\")\n\t}\n\tif !contains(perm, \"data2\") {\n\t\tt.Errorf(\"Test error: Bob cannot write data2\")\n\t}\n\tif contains(perm, \"data1\") {\n\t\tt.Errorf(\"Test error: Bob can write data1\")\n\t}\n\tif contains(perm, \"data_not_exist\") {\n\t\tt.Errorf(\"Test error: Bob can access a non-existing data\")\n\t}\n\n\t_, ok = bobTarget[\"rm_rf\"]\n\tif ok {\n\t\tt.Errorf(\"Someone can have a non-existing action (rm -rf)\")\n\t}\n}\n"
  },
  {
    "path": "frontend_test.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestCasbinJsGetPermissionForUser(t *testing.T) {\n\te, err := NewSyncedEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_with_hierarchy_policy.csv\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treceivedString, err := CasbinJsGetPermissionForUser(e, \"alice\") // make sure CasbinJsGetPermissionForUser can be used with a SyncedEnforcer.\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\treceived := map[string]interface{}{}\n\terr = json.Unmarshal([]byte(receivedString), &received)\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\texpectedModel, err := ioutil.ReadFile(\"examples/rbac_model.conf\")\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\t// Normalize line endings to \\n for cross-platform compatibility\n\texpectedModelStr := regexp.MustCompile(\"(\\r?\\n)+\").ReplaceAllString(string(expectedModel), \"\\n\")\n\tactualModelStr := strings.TrimSpace(received[\"m\"].(string))\n\texpectedModelStr = strings.TrimSpace(expectedModelStr)\n\n\tif actualModelStr != expectedModelStr {\n\t\tt.Errorf(\"%s supposed to be %s\", actualModelStr, expectedModelStr)\n\t}\n\n\texpectedPolicies, err := ioutil.ReadFile(\"examples/rbac_with_hierarchy_policy.csv\")\n\tif err != nil {\n\t\tt.Errorf(\"Test error: %s\", err)\n\t}\n\texpectedPoliciesItem := regexp.MustCompile(\",|\\n\").Split(string(expectedPolicies), -1)\n\ti := 0\n\tfor _, sArr := range received[\"p\"].([]interface{}) {\n\t\tfor _, s := range sArr.([]interface{}) {\n\t\t\tif strings.TrimSpace(s.(string)) != strings.TrimSpace(expectedPoliciesItem[i]) {\n\t\t\t\tt.Errorf(\"%s supposed to be %s\", strings.TrimSpace(s.(string)), strings.TrimSpace(expectedPoliciesItem[i]))\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/casbin/casbin/v3\n\nrequire (\n\tgithub.com/bmatcuk/doublestar/v4 v4.6.1\n\tgithub.com/casbin/govaluate v1.3.0\n\tgithub.com/google/uuid v1.6.0\n)\n\ngo 1.13\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=\ngithub.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=\ngithub.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=\ngithub.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\n"
  },
  {
    "path": "internal_api.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\n\tErr \"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/log\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\nconst (\n\tnotImplemented = \"not implemented\"\n)\n\nfunc (e *Enforcer) shouldPersist() bool {\n\treturn e.adapter != nil && e.autoSave\n}\n\nfunc (e *Enforcer) shouldNotify() bool {\n\treturn e.watcher != nil && e.autoNotifyWatcher\n}\n\n// validateConstraintsForGroupingPolicy validates constraints for grouping policy changes.\n// It returns an error if constraint validation fails.\nfunc (e *Enforcer) validateConstraintsForGroupingPolicy() error {\n\treturn e.model.ValidateConstraints()\n}\n\n// addPolicy adds a rule to the current policy.\nfunc (e *Enforcer) addPolicyWithoutNotify(sec string, ptype string, rule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.AddPolicies(sec, ptype, [][]string{rule})\n\t}\n\n\thasPolicy, err := e.model.HasPolicy(sec, ptype, rule)\n\tif hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err = e.adapter.AddPolicy(sec, ptype, rule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\terr = e.model.AddPolicy(sec, ptype, rule)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{rule})\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\n\t\t// Validate constraints after adding grouping policy\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// addPoliciesWithoutNotify adds rules to the current policy without notify\n// If autoRemoveRepeat == true, existing rules are automatically filtered\n// Otherwise, false is returned directly.\nfunc (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.AddPolicies(sec, ptype, rules)\n\t}\n\n\tif !autoRemoveRepeat {\n\t\thasPolicies, err := e.model.HasPolicies(sec, ptype, rules)\n\t\tif hasPolicies || err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.(persist.BatchAdapter).AddPolicies(sec, ptype, rules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\terr := e.model.AddPolicies(sec, ptype, rules)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\n\t\terr = e.BuildIncrementalConditionalRoleLinks(model.PolicyAdd, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\n\t\t// Validate constraints after adding grouping policies\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// removePolicy removes a rule from the current policy.\nfunc (e *Enforcer) removePolicyWithoutNotify(sec string, ptype string, rule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemovePolicies(sec, ptype, [][]string{rule})\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.RemovePolicy(sec, ptype, rule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleRemoved, err := e.model.RemovePolicy(sec, ptype, rule)\n\tif !ruleRemoved || err != nil {\n\t\treturn ruleRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{rule})\n\t\tif err != nil {\n\t\t\treturn ruleRemoved, err\n\t\t}\n\n\t\t// Validate constraints after removing grouping policy\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn ruleRemoved, nil\n}\n\nfunc (e *Enforcer) updatePolicyWithoutNotify(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.UpdatePolicy(sec, ptype, oldRule, newRule)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.(persist.UpdatableAdapter).UpdatePolicy(sec, ptype, oldRule, newRule); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\truleUpdated, err := e.model.UpdatePolicy(sec, ptype, oldRule, newRule)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, [][]string{oldRule}) // remove the old rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, [][]string{newRule}) // add the new rule\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\n\t\t// Validate constraints after updating grouping policy\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\nfunc (e *Enforcer) updatePoliciesWithoutNotify(sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\tif len(newRules) != len(oldRules) {\n\t\treturn 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))\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.UpdatePolicies(sec, ptype, oldRules, newRules)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.(persist.UpdatableAdapter).UpdatePolicies(sec, ptype, oldRules, newRules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleUpdated, err := e.model.UpdatePolicies(sec, ptype, oldRules, newRules)\n\tif !ruleUpdated || err != nil {\n\t\treturn ruleUpdated, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules\n\t\tif err != nil {\n\t\t\treturn ruleUpdated, err\n\t\t}\n\n\t\t// Validate constraints after updating grouping policies\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn ruleUpdated, nil\n}\n\n// removePolicies removes rules from the current policy.\nfunc (e *Enforcer) removePoliciesWithoutNotify(sec string, ptype string, rules [][]string) (bool, error) {\n\tif hasPolicies, err := e.model.HasPolicies(sec, ptype, rules); !hasPolicies || err != nil {\n\t\treturn hasPolicies, err\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemovePolicies(sec, ptype, rules)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.(persist.BatchAdapter).RemovePolicies(sec, ptype, rules); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\trulesRemoved, err := e.model.RemovePolicies(sec, ptype, rules)\n\tif !rulesRemoved || err != nil {\n\t\treturn rulesRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, rules)\n\t\tif err != nil {\n\t\t\treturn rulesRemoved, err\n\t\t}\n\n\t\t// Validate constraints after removing grouping policies\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\treturn rulesRemoved, nil\n}\n\n// removeFilteredPolicy removes rules based on field filters from the current policy.\nfunc (e *Enforcer) removeFilteredPolicyWithoutNotify(sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {\n\tif len(fieldValues) == 0 {\n\t\treturn false, Err.ErrInvalidFieldValuesParameter\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn true, e.dispatcher.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\t}\n\n\tif e.shouldPersist() {\n\t\tif err := e.adapter.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\n\truleRemoved, effects, err := e.model.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\tif !ruleRemoved || err != nil {\n\t\treturn ruleRemoved, err\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, effects)\n\t\tif err != nil {\n\t\t\treturn ruleRemoved, err\n\t\t}\n\n\t\t// Validate constraints after removing filtered grouping policies\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn ruleRemoved, nil\n}\n\nfunc (e *Enforcer) updateFilteredPoliciesWithoutNotify(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\tvar (\n\t\toldRules [][]string\n\t\terr      error\n\t)\n\n\tif _, err = e.model.GetAssertion(sec, ptype); err != nil {\n\t\treturn oldRules, err\n\t}\n\n\tif e.shouldPersist() {\n\t\tif oldRules, err = e.adapter.(persist.UpdatableAdapter).UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...); err != nil {\n\t\t\tif err.Error() != notImplemented {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\t// For compatibility, because some adapters return oldRules containing ptype, see https://github.com/casbin/xorm-adapter/issues/49\n\t\tfor i, oldRule := range oldRules {\n\t\t\tif len(oldRules[i]) == len(e.model[sec][ptype].Tokens)+1 {\n\t\t\t\toldRules[i] = oldRule[1:]\n\t\t\t}\n\t\t}\n\t}\n\n\tif e.dispatcher != nil && e.autoNotifyDispatcher {\n\t\treturn oldRules, e.dispatcher.UpdateFilteredPolicies(sec, ptype, oldRules, newRules)\n\t}\n\n\truleChanged, err := e.model.RemovePolicies(sec, ptype, oldRules)\n\tif err != nil {\n\t\treturn oldRules, err\n\t}\n\terr = e.model.AddPolicies(sec, ptype, newRules)\n\tif err != nil {\n\t\treturn oldRules, err\n\t}\n\truleChanged = ruleChanged && len(newRules) != 0\n\tif !ruleChanged {\n\t\treturn make([][]string, 0), nil\n\t}\n\n\tif sec == \"g\" {\n\t\terr := e.BuildIncrementalRoleLinks(model.PolicyRemove, ptype, oldRules) // remove the old rules\n\t\tif err != nil {\n\t\t\treturn oldRules, err\n\t\t}\n\t\terr = e.BuildIncrementalRoleLinks(model.PolicyAdd, ptype, newRules) // add the new rules\n\t\tif err != nil {\n\t\t\treturn oldRules, err\n\t\t}\n\n\t\t// Validate constraints after updating filtered grouping policies\n\t\tif err := e.validateConstraintsForGroupingPolicy(); err != nil {\n\t\t\treturn oldRules, err\n\t\t}\n\t}\n\n\treturn oldRules, nil\n}\n\n// addPolicy adds a rule to the current policy.\nfunc (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, error) {\n\tok, err := e.logPolicyOperation(log.EventAddPolicy, sec, rule, func() (bool, error) {\n\t\treturn e.addPolicyWithoutNotify(sec, ptype, rule)\n\t})\n\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar notifyErr error\n\t\tif watcher, isWatcherEx := e.watcher.(persist.WatcherEx); isWatcherEx {\n\t\t\tnotifyErr = watcher.UpdateForAddPolicy(sec, ptype, rule...)\n\t\t} else {\n\t\t\tnotifyErr = e.watcher.Update()\n\t\t}\n\t\treturn true, notifyErr\n\t}\n\n\treturn true, nil\n}\n\n// addPolicies adds rules to the current policy.\n// If autoRemoveRepeat == true, existing rules are automatically filtered\n// Otherwise, false is returned directly.\nfunc (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {\n\tok, err := e.addPoliciesWithoutNotify(sec, ptype, rules, autoRemoveRepeat)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForAddPolicies(sec, ptype, rules...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\n// removePolicy removes a rule from the current policy.\nfunc (e *Enforcer) removePolicy(sec string, ptype string, rule []string) (bool, error) {\n\tok, err := e.logPolicyOperation(log.EventRemovePolicy, sec, rule, func() (bool, error) {\n\t\treturn e.removePolicyWithoutNotify(sec, ptype, rule)\n\t})\n\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar notifyErr error\n\t\tif watcher, isWatcherEx := e.watcher.(persist.WatcherEx); isWatcherEx {\n\t\t\tnotifyErr = watcher.UpdateForRemovePolicy(sec, ptype, rule...)\n\t\t} else {\n\t\t\tnotifyErr = e.watcher.Update()\n\t\t}\n\t\treturn true, notifyErr\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *Enforcer) updatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {\n\tok, err := e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicy(sec, ptype, oldRule, newRule)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *Enforcer) updatePolicies(sec string, ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\tok, err := e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\n// removePolicies removes rules from the current policy.\nfunc (e *Enforcer) removePolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\tok, err := e.removePoliciesWithoutNotify(sec, ptype, rules)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForRemovePolicies(sec, ptype, rules...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\n// removeFilteredPolicy removes rules based on field filters from the current policy.\nfunc (e *Enforcer) removeFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues []string) (bool, error) {\n\tok, err := e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex, fieldValues)\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.WatcherEx); ok {\n\t\t\terr = watcher.UpdateForRemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *Enforcer) updateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\toldRules, err := e.updateFilteredPoliciesWithoutNotify(sec, ptype, newRules, fieldIndex, fieldValues...)\n\tok := len(oldRules) != 0\n\tif !ok || err != nil {\n\t\treturn ok, err\n\t}\n\n\tif e.shouldNotify() {\n\t\tvar err error\n\t\tif watcher, ok := e.watcher.(persist.UpdatableWatcher); ok {\n\t\t\terr = watcher.UpdateForUpdatePolicies(sec, ptype, oldRules, newRules)\n\t\t} else {\n\t\t\terr = e.watcher.Update()\n\t\t}\n\t\treturn true, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (e *Enforcer) GetFieldIndex(ptype string, field string) (int, error) {\n\treturn e.model.GetFieldIndex(ptype, field)\n}\n\nfunc (e *Enforcer) SetFieldIndex(ptype string, field string, index int) {\n\tassertion := e.model[\"p\"][ptype]\n\tassertion.FieldIndexMutex.Lock()\n\tassertion.FieldIndexMap[field] = index\n\tassertion.FieldIndexMutex.Unlock()\n}\n"
  },
  {
    "path": "lbac_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\nfunc testEnforceLBAC(t *testing.T, e *Enforcer, sub string, subConf, subInteg float64, obj string, objConf, objInteg float64, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, subConf, subInteg, obj, objConf, objInteg, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, conf=%v, integ=%v, %s, conf=%v, integ=%v, %s: %t, supposed to be %t\", sub, subConf, subInteg, obj, objConf, objInteg, act, myRes, res)\n\t}\n}\n\nfunc TestLBACModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/lbac_model.conf\")\n\n\tt.Log(\"Testing normal read operation scenarios\")\n\ttestEnforceLBAC(t, e, \"admin\", 5, 5, \"file_topsecret\", 3, 3, \"read\", true) // both high\n\ttestEnforceLBAC(t, e, \"manager\", 4, 4, \"file_secret\", 4, 2, \"read\", true)  // confidentiality equal, integrity higher\n\ttestEnforceLBAC(t, e, \"staff\", 3, 3, \"file_internal\", 2, 3, \"read\", true)  // confidentiality higher, integrity equal\n\ttestEnforceLBAC(t, e, \"guest\", 2, 2, \"file_public\", 2, 2, \"read\", true)    // both dimensions equal\n\n\tt.Log(\"Testing read operation violation scenarios\")\n\ttestEnforceLBAC(t, e, \"staff\", 3, 3, \"file_secret\", 4, 2, \"read\", false)      // insufficient confidentiality level\n\ttestEnforceLBAC(t, e, \"manager\", 4, 4, \"file_sensitive\", 3, 5, \"read\", false) // insufficient integrity level\n\ttestEnforceLBAC(t, e, \"guest\", 2, 2, \"file_internal\", 3, 1, \"read\", false)    // insufficient confidentiality level\n\ttestEnforceLBAC(t, e, \"staff\", 3, 3, \"file_protected\", 1, 4, \"read\", false)   // insufficient integrity level\n\n\tt.Log(\"Testing normal write operation scenarios\")\n\ttestEnforceLBAC(t, e, \"guest\", 2, 2, \"file_public\", 2, 2, \"write\", true)   // both dimensions equal\n\ttestEnforceLBAC(t, e, \"staff\", 3, 3, \"file_internal\", 5, 4, \"write\", true) // both low\n\ttestEnforceLBAC(t, e, \"manager\", 4, 4, \"file_secret\", 4, 5, \"write\", true) // confidentiality equal, integrity low\n\ttestEnforceLBAC(t, e, \"admin\", 5, 5, \"file_archive\", 5, 5, \"write\", true)  // both dimensions equal\n\n\tt.Log(\"Testing write operation violation scenarios\")\n\ttestEnforceLBAC(t, e, \"manager\", 4, 4, \"file_internal\", 3, 5, \"write\", false) // confidentiality level too high\n\ttestEnforceLBAC(t, e, \"staff\", 3, 3, \"file_public\", 2, 2, \"write\", false)     // both dimensions too high\n\ttestEnforceLBAC(t, e, \"admin\", 5, 5, \"file_secret\", 5, 4, \"write\", false)     // integrity level too high\n\ttestEnforceLBAC(t, e, \"guest\", 2, 2, \"file_private\", 1, 3, \"write\", false)    // confidentiality level too high\n}\n"
  },
  {
    "path": "log/default_logger.go",
    "content": "// Copyright 2026 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\n// DefaultLogger is the default implementation of the Logger interface.\ntype DefaultLogger struct {\n\toutput      io.Writer\n\teventTypes  map[EventType]bool\n\tlogCallback func(entry *LogEntry) error\n}\n\n// NewDefaultLogger creates a new DefaultLogger instance.\n// If no output is set via SetOutput, it defaults to os.Stdout.\nfunc NewDefaultLogger() *DefaultLogger {\n\treturn &DefaultLogger{\n\t\toutput:     os.Stdout,\n\t\teventTypes: make(map[EventType]bool),\n\t}\n}\n\n// SetOutput sets the output destination for the logger.\n// It can be set to a buffer or any io.Writer.\nfunc (l *DefaultLogger) SetOutput(w io.Writer) {\n\tif w != nil {\n\t\tl.output = w\n\t}\n}\n\n// SetEventTypes sets the event types that should be logged.\n// Only events matching these types will have IsActive set to true.\nfunc (l *DefaultLogger) SetEventTypes(eventTypes []EventType) error {\n\tl.eventTypes = make(map[EventType]bool)\n\tfor _, et := range eventTypes {\n\t\tl.eventTypes[et] = true\n\t}\n\treturn nil\n}\n\n// OnBeforeEvent is called before an event occurs.\n// It sets the StartTime and determines if the event should be active based on configured event types.\nfunc (l *DefaultLogger) OnBeforeEvent(entry *LogEntry) error {\n\tif entry == nil {\n\t\treturn fmt.Errorf(\"log entry is nil\")\n\t}\n\n\tentry.StartTime = time.Now()\n\n\t// Set IsActive based on whether this event type is enabled\n\t// If no event types are configured, all events are considered active\n\tif len(l.eventTypes) == 0 {\n\t\tentry.IsActive = true\n\t} else {\n\t\tentry.IsActive = l.eventTypes[entry.EventType]\n\t}\n\n\treturn nil\n}\n\n// OnAfterEvent is called after an event completes.\n// It calculates the duration, logs the entry if active, and calls the user callback if set.\nfunc (l *DefaultLogger) OnAfterEvent(entry *LogEntry) error {\n\tif entry == nil {\n\t\treturn fmt.Errorf(\"log entry is nil\")\n\t}\n\n\tentry.EndTime = time.Now()\n\tentry.Duration = entry.EndTime.Sub(entry.StartTime)\n\n\t// Only log if the event is active\n\tif entry.IsActive && l.output != nil {\n\t\tif err := l.writeLog(entry); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Call user-provided callback if set\n\tif l.logCallback != nil {\n\t\tif err := l.logCallback(entry); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// SetLogCallback sets a user-provided callback function.\n// The callback is called at the end of OnAfterEvent.\nfunc (l *DefaultLogger) SetLogCallback(callback func(entry *LogEntry) error) error {\n\tl.logCallback = callback\n\treturn nil\n}\n\n// writeLog writes the log entry to the configured output.\nfunc (l *DefaultLogger) writeLog(entry *LogEntry) error {\n\tvar logMessage string\n\n\tswitch entry.EventType {\n\tcase EventEnforce:\n\t\tlogMessage = fmt.Sprintf(\"[%s] Enforce: subject=%s, object=%s, action=%s, domain=%s, allowed=%v, duration=%v\\n\",\n\t\t\tentry.EventType, entry.Subject, entry.Object, entry.Action, entry.Domain, entry.Allowed, entry.Duration)\n\tcase EventAddPolicy, EventRemovePolicy:\n\t\tlogMessage = fmt.Sprintf(\"[%s] RuleCount=%d, duration=%v\\n\",\n\t\t\tentry.EventType, entry.RuleCount, entry.Duration)\n\tcase EventLoadPolicy, EventSavePolicy:\n\t\tlogMessage = fmt.Sprintf(\"[%s] RuleCount=%d, duration=%v\\n\",\n\t\t\tentry.EventType, entry.RuleCount, entry.Duration)\n\tdefault:\n\t\tlogMessage = fmt.Sprintf(\"[%s] duration=%v\\n\",\n\t\t\tentry.EventType, entry.Duration)\n\t}\n\n\tif entry.Error != nil {\n\t\tlogMessage = strings.TrimSuffix(logMessage, \"\\n\")\n\t\tlogMessage = fmt.Sprintf(\"%s Error: %v\\n\", logMessage, entry.Error)\n\t}\n\n\t_, err := l.output.Write([]byte(logMessage))\n\treturn err\n}\n"
  },
  {
    "path": "log/logger.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\n// Logger defines the interface for event-driven logging in Casbin.\ntype Logger interface {\n\tSetEventTypes([]EventType) error\n\t// OnBeforeEvent is called before an event occurs and returns a handle for context.\n\tOnBeforeEvent(entry *LogEntry) error\n\t// OnAfterEvent is called after an event completes with the handle and final entry.\n\tOnAfterEvent(entry *LogEntry) error\n\n\tSetLogCallback(func(entry *LogEntry) error) error\n}\n"
  },
  {
    "path": "log/types.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage log\n\nimport \"time\"\n\n// EventType represents the type of logging event.\ntype EventType string\n\n// Event type constants.\nconst (\n\tEventEnforce      EventType = \"enforce\"\n\tEventAddPolicy    EventType = \"addPolicy\"\n\tEventRemovePolicy EventType = \"removePolicy\"\n\tEventLoadPolicy   EventType = \"loadPolicy\"\n\tEventSavePolicy   EventType = \"savePolicy\"\n)\n\n// LogEntry represents a complete log entry for a Casbin event.\ntype LogEntry struct {\n\tIsActive bool\n\t// EventType is the type of the event being logged.\n\tEventType EventType\n\n\tStartTime time.Time\n\tEndTime   time.Time\n\tDuration  time.Duration\n\n\t// Enforce parameters.\n\t// Subject is the user or entity requesting access.\n\tSubject string\n\t// Object is the resource being accessed.\n\tObject string\n\t// Action is the operation being performed.\n\tAction string\n\t// Domain is the domain/tenant for multi-tenant scenarios.\n\tDomain string\n\t// Allowed indicates whether the enforcement request was allowed.\n\tAllowed bool\n\n\t// Rules contains the policy rules involved in the operation.\n\tRules [][]string\n\t// RuleCount is the number of rules affected by the operation.\n\tRuleCount int\n\n\t// Error contains any error that occurred during the event.\n\tError error\n}\n"
  },
  {
    "path": "logger_test.go",
    "content": "// Copyright 2026 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/log\"\n)\n\nfunc verifyBufferOutput(t *testing.T, logOutput string) {\n\tt.Helper()\n\texpectedEvents := []string{\"[enforce]\", \"[addPolicy]\", \"[removePolicy]\", \"[savePolicy]\", \"[loadPolicy]\"}\n\tfor _, event := range expectedEvents {\n\t\tif !strings.Contains(logOutput, event) {\n\t\t\tt.Errorf(\"Expected log output to contain %s event\", event)\n\t\t}\n\t}\n}\n\nfunc verifyCallbackEntries(t *testing.T, entries []*log.LogEntry) {\n\tt.Helper()\n\tfound := map[log.EventType]bool{}\n\n\tfor _, entry := range entries {\n\t\tfound[entry.EventType] = true\n\t\tswitch entry.EventType {\n\t\tcase log.EventEnforce:\n\t\t\tif entry.Subject == \"\" && entry.Object == \"\" && entry.Action == \"\" {\n\t\t\t\tt.Errorf(\"Expected enforce entry to have subject, object, and action\")\n\t\t\t}\n\t\tcase log.EventAddPolicy, log.EventRemovePolicy:\n\t\t\tif entry.RuleCount != 1 {\n\t\t\t\tt.Errorf(\"Expected %s entry to have RuleCount=1, got %d\", entry.EventType, entry.RuleCount)\n\t\t\t}\n\t\tcase log.EventSavePolicy, log.EventLoadPolicy:\n\t\t\tif entry.RuleCount == 0 {\n\t\t\t\tt.Errorf(\"Expected %s entry to have RuleCount>0\", entry.EventType)\n\t\t\t}\n\t\t}\n\t}\n\n\trequiredEvents := []log.EventType{\n\t\tlog.EventEnforce, log.EventAddPolicy, log.EventRemovePolicy,\n\t\tlog.EventSavePolicy, log.EventLoadPolicy,\n\t}\n\tfor _, eventType := range requiredEvents {\n\t\tif !found[eventType] {\n\t\t\tt.Errorf(\"Expected to find %s in callback entries\", eventType)\n\t\t}\n\t}\n}\n\nfunc TestEnforcerWithDefaultLogger(t *testing.T) {\n\t// Create enforcer with RBAC model and policy\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Create a buffer to capture log output\n\tvar buf bytes.Buffer\n\tlogger := log.NewDefaultLogger()\n\tlogger.SetOutput(&buf)\n\n\t// Set up a callback to track log entries\n\tvar callbackEntries []*log.LogEntry\n\terr = logger.SetLogCallback(func(entry *log.LogEntry) error {\n\t\t// Create a copy of the entry to store\n\t\tentryCopy := *entry\n\t\tcallbackEntries = append(callbackEntries, &entryCopy)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to set log callback: %v\", err)\n\t}\n\n\t// Set the logger on the enforcer\n\te.SetLogger(logger)\n\n\t// Test Enforce events\n\tif result, err := e.Enforce(\"alice\", \"data1\", \"read\"); err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t} else if !result {\n\t\tt.Errorf(\"Expected alice to have read access to data1\")\n\t}\n\n\tif result, err := e.Enforce(\"bob\", \"data2\", \"write\"); err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t} else if !result {\n\t\tt.Errorf(\"Expected bob to have write access to data2\")\n\t}\n\n\t// Test AddPolicy event\n\tif added, err := e.AddPolicy(\"charlie\", \"data3\", \"read\"); err != nil {\n\t\tt.Fatalf(\"AddPolicy failed: %v\", err)\n\t} else if !added {\n\t\tt.Errorf(\"Expected policy to be added\")\n\t}\n\n\t// Test RemovePolicy event\n\tif removed, err := e.RemovePolicy(\"charlie\", \"data3\", \"read\"); err != nil {\n\t\tt.Fatalf(\"RemovePolicy failed: %v\", err)\n\t} else if !removed {\n\t\tt.Errorf(\"Expected policy to be removed\")\n\t}\n\n\t// Test SavePolicy and LoadPolicy events\n\tif err := e.SavePolicy(); err != nil {\n\t\tt.Fatalf(\"SavePolicy failed: %v\", err)\n\t}\n\tif err := e.LoadPolicy(); err != nil {\n\t\tt.Fatalf(\"LoadPolicy failed: %v\", err)\n\t}\n\n\t// Verify buffer output and callback entries\n\tverifyBufferOutput(t, buf.String())\n\n\tif len(callbackEntries) == 0 {\n\t\tt.Fatalf(\"Expected callback to be called, but got no entries\")\n\t}\n\tverifyCallbackEntries(t, callbackEntries)\n}\n\nfunc TestSetEventTypes(t *testing.T) {\n\t// Create enforcer with RBAC model and policy\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Create a buffer to capture log output\n\tvar buf bytes.Buffer\n\tlogger := log.NewDefaultLogger()\n\tlogger.SetOutput(&buf)\n\n\t// Set up a callback to track log entries\n\tvar callbackEntries []*log.LogEntry\n\terr = logger.SetLogCallback(func(entry *log.LogEntry) error {\n\t\t// Create a copy of the entry to store\n\t\tentryCopy := *entry\n\t\tcallbackEntries = append(callbackEntries, &entryCopy)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to set log callback: %v\", err)\n\t}\n\n\t// Configure logger to only log EventEnforce and EventAddPolicy\n\terr = logger.SetEventTypes([]log.EventType{log.EventEnforce, log.EventAddPolicy})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to set event types: %v\", err)\n\t}\n\n\t// Set the logger on the enforcer\n\te.SetLogger(logger)\n\n\t// Perform various operations\n\t_, err = e.Enforce(\"alice\", \"data1\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"Enforce failed: %v\", err)\n\t}\n\n\t_, err = e.AddPolicy(\"charlie\", \"data3\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPolicy failed: %v\", err)\n\t}\n\n\t_, err = e.RemovePolicy(\"charlie\", \"data3\", \"read\")\n\tif err != nil {\n\t\tt.Fatalf(\"RemovePolicy failed: %v\", err)\n\t}\n\n\tif err := e.LoadPolicy(); err != nil {\n\t\tt.Fatalf(\"LoadPolicy failed: %v\", err)\n\t}\n\n\t// Verify buffer output only contains EventEnforce and EventAddPolicy\n\tverifySelectiveBufferOutput(t, buf.String())\n\n\t// Verify callback entries\n\tverifySelectiveCallbackEntries(t, callbackEntries)\n}\n\nfunc verifySelectiveBufferOutput(t *testing.T, logOutput string) {\n\tt.Helper()\n\tif !strings.Contains(logOutput, \"[enforce]\") {\n\t\tt.Errorf(\"Expected log output to contain enforce events\")\n\t}\n\tif !strings.Contains(logOutput, \"[addPolicy]\") {\n\t\tt.Errorf(\"Expected log output to contain addPolicy event\")\n\t}\n\tif strings.Contains(logOutput, \"[removePolicy]\") {\n\t\tt.Errorf(\"Did not expect log output to contain removePolicy event\")\n\t}\n\tif strings.Contains(logOutput, \"[loadPolicy]\") {\n\t\tt.Errorf(\"Did not expect log output to contain loadPolicy event\")\n\t}\n}\n\nfunc verifySelectiveCallbackEntries(t *testing.T, entries []*log.LogEntry) {\n\tt.Helper()\n\tfound := map[log.EventType]bool{}\n\n\tfor _, entry := range entries {\n\t\tfound[entry.EventType] = true\n\t\tcheckEntryActiveStatus(t, entry)\n\t}\n\n\trequiredEvents := []log.EventType{\n\t\tlog.EventEnforce, log.EventAddPolicy, log.EventRemovePolicy, log.EventLoadPolicy,\n\t}\n\tfor _, eventType := range requiredEvents {\n\t\tif !found[eventType] {\n\t\t\tt.Errorf(\"Expected to find %s in callback entries\", eventType)\n\t\t}\n\t}\n}\n\nfunc checkEntryActiveStatus(t *testing.T, entry *log.LogEntry) {\n\tt.Helper()\n\tswitch entry.EventType {\n\tcase log.EventEnforce, log.EventAddPolicy:\n\t\tif !entry.IsActive {\n\t\t\tt.Errorf(\"Expected %s entry to be active\", entry.EventType)\n\t\t}\n\tcase log.EventRemovePolicy, log.EventLoadPolicy:\n\t\tif entry.IsActive {\n\t\t\tt.Errorf(\"Expected %s entry to be inactive\", entry.EventType)\n\t\t}\n\tcase log.EventSavePolicy:\n\t\t// SavePolicy event exists but we're not checking it in this test\n\t}\n}\n"
  },
  {
    "path": "management_api.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/util\"\n\t\"github.com/casbin/govaluate\"\n)\n\n// GetAllSubjects gets the list of subjects that show up in the current policy.\nfunc (e *Enforcer) GetAllSubjects() ([]string, error) {\n\treturn e.model.GetValuesForFieldInPolicyAllTypesByName(\"p\", constant.SubjectIndex)\n}\n\n// GetAllNamedSubjects gets the list of subjects that show up in the current named policy.\nfunc (e *Enforcer) GetAllNamedSubjects(ptype string) ([]string, error) {\n\tfieldIndex, err := e.model.GetFieldIndex(ptype, constant.SubjectIndex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn e.model.GetValuesForFieldInPolicy(\"p\", ptype, fieldIndex)\n}\n\n// GetAllObjects gets the list of objects that show up in the current policy.\nfunc (e *Enforcer) GetAllObjects() ([]string, error) {\n\treturn e.model.GetValuesForFieldInPolicyAllTypesByName(\"p\", constant.ObjectIndex)\n}\n\n// GetAllNamedObjects gets the list of objects that show up in the current named policy.\nfunc (e *Enforcer) GetAllNamedObjects(ptype string) ([]string, error) {\n\tfieldIndex, err := e.model.GetFieldIndex(ptype, constant.ObjectIndex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn e.model.GetValuesForFieldInPolicy(\"p\", ptype, fieldIndex)\n}\n\n// GetAllActions gets the list of actions that show up in the current policy.\nfunc (e *Enforcer) GetAllActions() ([]string, error) {\n\treturn e.model.GetValuesForFieldInPolicyAllTypesByName(\"p\", constant.ActionIndex)\n}\n\n// GetAllNamedActions gets the list of actions that show up in the current named policy.\nfunc (e *Enforcer) GetAllNamedActions(ptype string) ([]string, error) {\n\tfieldIndex, err := e.model.GetFieldIndex(ptype, constant.ActionIndex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn e.model.GetValuesForFieldInPolicy(\"p\", ptype, fieldIndex)\n}\n\n// GetAllRoles gets the list of roles that show up in the current policy.\nfunc (e *Enforcer) GetAllRoles() ([]string, error) {\n\treturn e.model.GetValuesForFieldInPolicyAllTypes(\"g\", 1)\n}\n\n// GetAllNamedRoles gets the list of roles that show up in the current named policy.\nfunc (e *Enforcer) GetAllNamedRoles(ptype string) ([]string, error) {\n\treturn e.model.GetValuesForFieldInPolicy(\"g\", ptype, 1)\n}\n\n// GetAllUsers gets the list of users that show up in the current policy.\n// Users are subjects that are not roles (i.e., subjects that do not appear as the second element in any grouping policy).\nfunc (e *Enforcer) GetAllUsers() ([]string, error) {\n\tsubjects, err := e.GetAllSubjects()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\troles, err := e.GetAllRoles()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tusers := util.SetSubtract(subjects, roles)\n\treturn users, nil\n}\n\n// GetPolicy gets all the authorization rules in the policy.\nfunc (e *Enforcer) GetPolicy() ([][]string, error) {\n\treturn e.GetNamedPolicy(\"p\")\n}\n\n// GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified.\nfunc (e *Enforcer) GetFilteredPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {\n\treturn e.GetFilteredNamedPolicy(\"p\", fieldIndex, fieldValues...)\n}\n\n// GetNamedPolicy gets all the authorization rules in the named policy.\nfunc (e *Enforcer) GetNamedPolicy(ptype string) ([][]string, error) {\n\treturn e.model.GetPolicy(\"p\", ptype)\n}\n\n// GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified.\nfunc (e *Enforcer) GetFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\treturn e.model.GetFilteredPolicy(\"p\", ptype, fieldIndex, fieldValues...)\n}\n\n// GetGroupingPolicy gets all the role inheritance rules in the policy.\nfunc (e *Enforcer) GetGroupingPolicy() ([][]string, error) {\n\treturn e.GetNamedGroupingPolicy(\"g\")\n}\n\n// GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.\nfunc (e *Enforcer) GetFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {\n\treturn e.GetFilteredNamedGroupingPolicy(\"g\", fieldIndex, fieldValues...)\n}\n\n// GetNamedGroupingPolicy gets all the role inheritance rules in the policy.\nfunc (e *Enforcer) GetNamedGroupingPolicy(ptype string) ([][]string, error) {\n\treturn e.model.GetPolicy(\"g\", ptype)\n}\n\n// GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified.\nfunc (e *Enforcer) GetFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\treturn e.model.GetFilteredPolicy(\"g\", ptype, fieldIndex, fieldValues...)\n}\n\n// GetFilteredNamedPolicyWithMatcher gets rules based on matcher from the policy.\nfunc (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype string, matcher string) ([][]string, error) {\n\tvar res [][]string\n\tvar err error\n\n\tfunctions := e.fm.GetFunctions()\n\tif _, ok := e.model[\"g\"]; ok {\n\t\tfor key, ast := range e.model[\"g\"] {\n\t\t\t// g must be a normal role definition (ast.RM != nil)\n\t\t\t//   or a conditional role definition (ast.CondRM != nil)\n\t\t\t// ast.RM and ast.CondRM shouldn't be nil at the same time\n\t\t\tif ast.RM != nil {\n\t\t\t\tfunctions[key] = util.GenerateGFunction(ast.RM)\n\t\t\t}\n\t\t\tif ast.CondRM != nil {\n\t\t\t\tfunctions[key] = util.GenerateConditionalGFunction(ast.CondRM)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar expString string\n\tif matcher == \"\" {\n\t\treturn res, fmt.Errorf(\"matcher is empty\")\n\t} else {\n\t\texpString = util.RemoveComments(util.EscapeAssertion(matcher))\n\t}\n\n\tvar expression *govaluate.EvaluableExpression\n\n\texpression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions)\n\tif err != nil {\n\t\treturn res, err\n\t}\n\n\tpTokens := make(map[string]int, len(e.model[\"p\"][ptype].Tokens))\n\tfor i, token := range e.model[\"p\"][ptype].Tokens {\n\t\tpTokens[token] = i\n\t}\n\n\tparameters := enforceParameters{\n\t\tpTokens: pTokens,\n\t}\n\n\tif policyLen := len(e.model[\"p\"][ptype].Policy); policyLen != 0 && strings.Contains(expString, ptype+\"_\") {\n\t\tfor _, pvals := range e.model[\"p\"][ptype].Policy {\n\t\t\tif len(e.model[\"p\"][ptype].Tokens) != len(pvals) {\n\t\t\t\treturn res, fmt.Errorf(\n\t\t\t\t\t\"invalid policy size: expected %d, got %d, pvals: %v\",\n\t\t\t\t\tlen(e.model[\"p\"][ptype].Tokens),\n\t\t\t\t\tlen(pvals),\n\t\t\t\t\tpvals)\n\t\t\t}\n\n\t\t\tparameters.pVals = pvals\n\n\t\t\tresult, err := expression.Eval(parameters)\n\n\t\t\tif err != nil {\n\t\t\t\treturn res, err\n\t\t\t}\n\n\t\t\tswitch result := result.(type) {\n\t\t\tcase bool:\n\t\t\t\tif result {\n\t\t\t\t\tres = append(res, pvals)\n\t\t\t\t}\n\t\t\tcase float64:\n\t\t\t\tif result != 0 {\n\t\t\t\t\tres = append(res, pvals)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn res, errors.New(\"matcher result should be bool, int or float\")\n\t\t\t}\n\t\t}\n\t}\n\treturn res, nil\n}\n\n// HasPolicy determines whether an authorization rule exists.\nfunc (e *Enforcer) HasPolicy(params ...interface{}) (bool, error) {\n\treturn e.HasNamedPolicy(\"p\", params...)\n}\n\n// HasNamedPolicy determines whether a named authorization rule exists.\nfunc (e *Enforcer) HasNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\treturn e.model.HasPolicy(\"p\", ptype, strSlice)\n\t}\n\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.model.HasPolicy(\"p\", ptype, policy)\n}\n\n// AddPolicy adds an authorization rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *Enforcer) AddPolicy(params ...interface{}) (bool, error) {\n\treturn e.AddNamedPolicy(\"p\", params...)\n}\n\n// AddPolicies adds authorization rules to the current policy.\n// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding rule by adding the new rule.\nfunc (e *Enforcer) AddPolicies(rules [][]string) (bool, error) {\n\treturn e.AddNamedPolicies(\"p\", rules)\n}\n\n// AddPoliciesEx adds authorization rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *Enforcer) AddPoliciesEx(rules [][]string) (bool, error) {\n\treturn e.AddNamedPoliciesEx(\"p\", rules)\n}\n\n// AddNamedPolicy adds an authorization rule to the current named policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *Enforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\tstrSlice = append(make([]string, 0, len(strSlice)), strSlice...)\n\t\treturn e.addPolicy(\"p\", ptype, strSlice)\n\t}\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.addPolicy(\"p\", ptype, policy)\n}\n\n// AddNamedPolicies adds authorization rules to the current named policy.\n// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding by adding the new rule.\nfunc (e *Enforcer) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\treturn e.addPolicies(\"p\", ptype, rules, false)\n}\n\n// AddNamedPoliciesEx adds authorization rules to the current named policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *Enforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {\n\treturn e.addPolicies(\"p\", ptype, rules, true)\n}\n\n// RemovePolicy removes an authorization rule from the current policy.\nfunc (e *Enforcer) RemovePolicy(params ...interface{}) (bool, error) {\n\treturn e.RemoveNamedPolicy(\"p\", params...)\n}\n\n// UpdatePolicy updates an authorization rule from the current policy.\nfunc (e *Enforcer) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {\n\treturn e.UpdateNamedPolicy(\"p\", oldPolicy, newPolicy)\n}\n\nfunc (e *Enforcer) UpdateNamedPolicy(ptype string, p1 []string, p2 []string) (bool, error) {\n\treturn e.updatePolicy(\"p\", ptype, p1, p2)\n}\n\n// UpdatePolicies updates authorization rules from the current policies.\nfunc (e *Enforcer) UpdatePolicies(oldPolices [][]string, newPolicies [][]string) (bool, error) {\n\treturn e.UpdateNamedPolicies(\"p\", oldPolices, newPolicies)\n}\n\nfunc (e *Enforcer) UpdateNamedPolicies(ptype string, p1 [][]string, p2 [][]string) (bool, error) {\n\treturn e.updatePolicies(\"p\", ptype, p1, p2)\n}\n\nfunc (e *Enforcer) UpdateFilteredPolicies(newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.UpdateFilteredNamedPolicies(\"p\", newPolicies, fieldIndex, fieldValues...)\n}\n\nfunc (e *Enforcer) UpdateFilteredNamedPolicies(ptype string, newPolicies [][]string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.updateFilteredPolicies(\"p\", ptype, newPolicies, fieldIndex, fieldValues...)\n}\n\n// RemovePolicies removes authorization rules from the current policy.\nfunc (e *Enforcer) RemovePolicies(rules [][]string) (bool, error) {\n\treturn e.RemoveNamedPolicies(\"p\", rules)\n}\n\n// RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified.\nfunc (e *Enforcer) RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.RemoveFilteredNamedPolicy(\"p\", fieldIndex, fieldValues...)\n}\n\n// RemoveNamedPolicy removes an authorization rule from the current named policy.\nfunc (e *Enforcer) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\treturn e.removePolicy(\"p\", ptype, strSlice)\n\t}\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.removePolicy(\"p\", ptype, policy)\n}\n\n// RemoveNamedPolicies removes authorization rules from the current named policy.\nfunc (e *Enforcer) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\treturn e.removePolicies(\"p\", ptype, rules)\n}\n\n// RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified.\nfunc (e *Enforcer) RemoveFilteredNamedPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicy(\"p\", ptype, fieldIndex, fieldValues)\n}\n\n// HasGroupingPolicy determines whether a role inheritance rule exists.\nfunc (e *Enforcer) HasGroupingPolicy(params ...interface{}) (bool, error) {\n\treturn e.HasNamedGroupingPolicy(\"g\", params...)\n}\n\n// HasNamedGroupingPolicy determines whether a named role inheritance rule exists.\nfunc (e *Enforcer) HasNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\treturn e.model.HasPolicy(\"g\", ptype, strSlice)\n\t}\n\n\tpolicy := make([]string, 0)\n\tfor _, param := range params {\n\t\tpolicy = append(policy, param.(string))\n\t}\n\n\treturn e.model.HasPolicy(\"g\", ptype, policy)\n}\n\n// AddGroupingPolicy adds a role inheritance rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *Enforcer) AddGroupingPolicy(params ...interface{}) (bool, error) {\n\treturn e.AddNamedGroupingPolicy(\"g\", params...)\n}\n\n// AddGroupingPolicies adds role inheritance rules to the current policy.\n// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding policy rule by adding the new rule.\nfunc (e *Enforcer) AddGroupingPolicies(rules [][]string) (bool, error) {\n\treturn e.AddNamedGroupingPolicies(\"g\", rules)\n}\n\n// AddGroupingPoliciesEx adds role inheritance rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *Enforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {\n\treturn e.AddNamedGroupingPoliciesEx(\"g\", rules)\n}\n\n// AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.\n// If the rule already exists, the function returns false and the rule will not be added.\n// Otherwise the function returns true by adding the new rule.\nfunc (e *Enforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\tvar ruleAdded bool\n\tvar err error\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\truleAdded, err = e.addPolicy(\"g\", ptype, strSlice)\n\t} else {\n\t\tpolicy := make([]string, 0)\n\t\tfor _, param := range params {\n\t\t\tpolicy = append(policy, param.(string))\n\t\t}\n\n\t\truleAdded, err = e.addPolicy(\"g\", ptype, policy)\n\t}\n\n\treturn ruleAdded, err\n}\n\n// AddNamedGroupingPolicies adds named role inheritance rules to the current policy.\n// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.\n// Otherwise the function returns true for the corresponding policy rule by adding the new rule.\nfunc (e *Enforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {\n\treturn e.addPolicies(\"g\", ptype, rules, false)\n}\n\n// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.\n// If the rule already exists, the rule will not be added.\n// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly.\nfunc (e *Enforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {\n\treturn e.addPolicies(\"g\", ptype, rules, true)\n}\n\n// RemoveGroupingPolicy removes a role inheritance rule from the current policy.\nfunc (e *Enforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) {\n\treturn e.RemoveNamedGroupingPolicy(\"g\", params...)\n}\n\n// RemoveGroupingPolicies removes role inheritance rules from the current policy.\nfunc (e *Enforcer) RemoveGroupingPolicies(rules [][]string) (bool, error) {\n\treturn e.RemoveNamedGroupingPolicies(\"g\", rules)\n}\n\n// RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified.\nfunc (e *Enforcer) RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.RemoveFilteredNamedGroupingPolicy(\"g\", fieldIndex, fieldValues...)\n}\n\n// RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy.\nfunc (e *Enforcer) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\tvar ruleRemoved bool\n\tvar err error\n\tif strSlice, ok := params[0].([]string); len(params) == 1 && ok {\n\t\truleRemoved, err = e.removePolicy(\"g\", ptype, strSlice)\n\t} else {\n\t\tpolicy := make([]string, 0)\n\t\tfor _, param := range params {\n\t\t\tpolicy = append(policy, param.(string))\n\t\t}\n\n\t\truleRemoved, err = e.removePolicy(\"g\", ptype, policy)\n\t}\n\n\treturn ruleRemoved, err\n}\n\n// RemoveNamedGroupingPolicies removes role inheritance rules from the current named policy.\nfunc (e *Enforcer) RemoveNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {\n\treturn e.removePolicies(\"g\", ptype, rules)\n}\n\nfunc (e *Enforcer) UpdateGroupingPolicy(oldRule []string, newRule []string) (bool, error) {\n\treturn e.UpdateNamedGroupingPolicy(\"g\", oldRule, newRule)\n}\n\n// UpdateGroupingPolicies updates authorization rules from the current policies.\nfunc (e *Enforcer) UpdateGroupingPolicies(oldRules [][]string, newRules [][]string) (bool, error) {\n\treturn e.UpdateNamedGroupingPolicies(\"g\", oldRules, newRules)\n}\n\nfunc (e *Enforcer) UpdateNamedGroupingPolicy(ptype string, oldRule []string, newRule []string) (bool, error) {\n\treturn e.updatePolicy(\"g\", ptype, oldRule, newRule)\n}\n\nfunc (e *Enforcer) UpdateNamedGroupingPolicies(ptype string, oldRules [][]string, newRules [][]string) (bool, error) {\n\treturn e.updatePolicies(\"g\", ptype, oldRules, newRules)\n}\n\n// RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified.\nfunc (e *Enforcer) RemoveFilteredNamedGroupingPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicy(\"g\", ptype, fieldIndex, fieldValues)\n}\n\n// AddFunction adds a customized function.\nfunc (e *Enforcer) AddFunction(name string, function govaluate.ExpressionFunction) {\n\te.fm.AddFunction(name, function)\n}\n\nfunc (e *Enforcer) SelfAddPolicy(sec string, ptype string, rule []string) (bool, error) {\n\treturn e.addPolicyWithoutNotify(sec, ptype, rule)\n}\n\nfunc (e *Enforcer) SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesWithoutNotify(sec, ptype, rules, false)\n}\n\nfunc (e *Enforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.addPoliciesWithoutNotify(sec, ptype, rules, true)\n}\n\nfunc (e *Enforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {\n\treturn e.removePolicyWithoutNotify(sec, ptype, rule)\n}\n\nfunc (e *Enforcer) SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\treturn e.removePoliciesWithoutNotify(sec, ptype, rules)\n}\n\nfunc (e *Enforcer) SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error) {\n\treturn e.removeFilteredPolicyWithoutNotify(sec, ptype, fieldIndex, fieldValues)\n}\n\nfunc (e *Enforcer) SelfUpdatePolicy(sec string, ptype string, oldRule, newRule []string) (bool, error) {\n\treturn e.updatePolicyWithoutNotify(sec, ptype, oldRule, newRule)\n}\n\nfunc (e *Enforcer) SelfUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {\n\treturn e.updatePoliciesWithoutNotify(sec, ptype, oldRules, newRules)\n}\n"
  },
  {
    "path": "management_api_b_test.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//\thttp://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\npackage casbin\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"testing\"\n)\n\nfunc BenchmarkHasPolicySmall(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 100 roles, 10 resources.\n\tfor i := 0; i < 100; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\")\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\te.HasPolicy(fmt.Sprintf(\"user%d\", rand.Intn(100)), fmt.Sprintf(\"data%d\", rand.Intn(100)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkHasPolicyMedium(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\te.HasPolicy(fmt.Sprintf(\"user%d\", rand.Intn(1000)), fmt.Sprintf(\"data%d\", rand.Intn(1000)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkHasPolicyLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\te.HasPolicy(fmt.Sprintf(\"user%d\", rand.Intn(10000)), fmt.Sprintf(\"data%d\", rand.Intn(10000)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkAddPolicySmall(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 100 roles, 10 resources.\n\tfor i := 0; i < 100; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\")\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", rand.Intn(100)+100), fmt.Sprintf(\"data%d\", (rand.Intn(100)+100)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkAddPolicyMedium(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", rand.Intn(1000)+1000), fmt.Sprintf(\"data%d\", (rand.Intn(1000)+1000)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkAddPolicyLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", rand.Intn(10000)+10000), fmt.Sprintf(\"data%d\", (rand.Intn(10000)+10000)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkRemovePolicySmall(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 100 roles, 10 resources.\n\tfor i := 0; i < 100; i++ {\n\t\t_, _ = e.AddPolicy(fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\")\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.RemovePolicy(fmt.Sprintf(\"user%d\", rand.Intn(100)), fmt.Sprintf(\"data%d\", rand.Intn(100)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkRemovePolicyMedium(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.RemovePolicy(fmt.Sprintf(\"user%d\", rand.Intn(1000)), fmt.Sprintf(\"data%d\", rand.Intn(1000)/10), \"read\")\n\t}\n}\n\nfunc BenchmarkRemovePolicyLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.RemovePolicy(fmt.Sprintf(\"user%d\", rand.Intn(10000)), fmt.Sprintf(\"data%d\", rand.Intn(10000)/10), \"read\")\n\t}\n}\n"
  },
  {
    "path": "management_api_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc testStringList(t *testing.T, title string, f func() ([]string, error), res []string) {\n\tt.Helper()\n\tmyRes, err := f()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(title+\": \", myRes)\n\n\tif !util.ArrayEquals(res, myRes) {\n\t\tt.Error(title+\": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetList(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestStringList(t, \"Subjects\", e.GetAllSubjects, []string{\"alice\", \"bob\", \"data2_admin\"})\n\ttestStringList(t, \"Objects\", e.GetAllObjects, []string{\"data1\", \"data2\"})\n\ttestStringList(t, \"Actions\", e.GetAllActions, []string{\"read\", \"write\"})\n\ttestStringList(t, \"Roles\", e.GetAllRoles, []string{\"data2_admin\"})\n\ttestStringList(t, \"Users\", e.GetAllUsers, []string{\"alice\", \"bob\"})\n}\n\nfunc TestGetListWithDomains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestStringList(t, \"Subjects\", e.GetAllSubjects, []string{\"admin\"})\n\ttestStringList(t, \"Objects\", e.GetAllObjects, []string{\"data1\", \"data2\"})\n\ttestStringList(t, \"Actions\", e.GetAllActions, []string{\"read\", \"write\"})\n\ttestStringList(t, \"Roles\", e.GetAllRoles, []string{\"admin\"})\n\ttestStringList(t, \"Users\", e.GetAllUsers, []string{})\n}\n\nfunc testGetPolicy(t *testing.T, e *Enforcer, res [][]string) {\n\tt.Helper()\n\tmyRes, err := e.GetPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Policy: \", myRes)\n\n\tif !util.SortedArray2DEquals(res, myRes) {\n\t\tt.Error(\"Policy: \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetFilteredPolicy(t *testing.T, e *Enforcer, fieldIndex int, res [][]string, fieldValues ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetFilteredPolicy(fieldIndex, fieldValues...)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Policy for \", util.ParamsToString(fieldValues...), \": \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Policy for \", util.ParamsToString(fieldValues...), \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetFilteredNamedPolicyWithMatcher(t *testing.T, e *Enforcer, ptype string, matcher string, res [][]string) {\n\tt.Helper()\n\tmyRes, err := e.GetFilteredNamedPolicyWithMatcher(ptype, matcher)\n\tt.Log(\"Policy for\", matcher, \": \", myRes)\n\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Policy for \", matcher, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetGroupingPolicy(t *testing.T, e *Enforcer, res [][]string) {\n\tt.Helper()\n\tmyRes, err := e.GetGroupingPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Grouping policy: \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Grouping policy: \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetFilteredGroupingPolicy(t *testing.T, e *Enforcer, fieldIndex int, res [][]string, fieldValues ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetFilteredGroupingPolicy(fieldIndex, fieldValues...)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Grouping policy for \", util.ParamsToString(fieldValues...), \": \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Grouping policy for \", util.ParamsToString(fieldValues...), \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testHasPolicy(t *testing.T, e *Enforcer, policy []string, res bool) {\n\tt.Helper()\n\tmyRes, err := e.HasPolicy(policy)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Has policy \", util.ArrayToString(policy), \": \", myRes)\n\n\tif res != myRes {\n\t\tt.Error(\"Has policy \", util.ArrayToString(policy), \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testHasGroupingPolicy(t *testing.T, e *Enforcer, policy []string, res bool) {\n\tt.Helper()\n\tmyRes, err := e.HasGroupingPolicy(policy)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tt.Log(\"Has grouping policy \", util.ArrayToString(policy), \": \", myRes)\n\n\tif res != myRes {\n\t\tt.Error(\"Has grouping policy \", util.ArrayToString(policy), \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetPolicyAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestGetPolicy(t, e, [][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"}})\n\n\ttestGetFilteredPolicy(t, e, 0, [][]string{{\"alice\", \"data1\", \"read\"}}, \"alice\")\n\ttestGetFilteredPolicy(t, e, 0, [][]string{{\"bob\", \"data2\", \"write\"}}, \"bob\")\n\ttestGetFilteredPolicy(t, e, 0, [][]string{{\"data2_admin\", \"data2\", \"read\"}, {\"data2_admin\", \"data2\", \"write\"}}, \"data2_admin\")\n\ttestGetFilteredPolicy(t, e, 1, [][]string{{\"alice\", \"data1\", \"read\"}}, \"data1\")\n\ttestGetFilteredPolicy(t, e, 1, [][]string{{\"bob\", \"data2\", \"write\"}, {\"data2_admin\", \"data2\", \"read\"}, {\"data2_admin\", \"data2\", \"write\"}}, \"data2\")\n\ttestGetFilteredPolicy(t, e, 2, [][]string{{\"alice\", \"data1\", \"read\"}, {\"data2_admin\", \"data2\", \"read\"}}, \"read\")\n\ttestGetFilteredPolicy(t, e, 2, [][]string{{\"bob\", \"data2\", \"write\"}, {\"data2_admin\", \"data2\", \"write\"}}, \"write\")\n\n\ttestGetFilteredNamedPolicyWithMatcher(t, e, \"p\", \"'alice' == p.sub\", [][]string{{\"alice\", \"data1\", \"read\"}})\n\ttestGetFilteredNamedPolicyWithMatcher(t, e, \"p\", \"keyMatch2(p.sub, '*')\", [][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"}})\n\n\ttestGetFilteredPolicy(t, e, 0, [][]string{{\"data2_admin\", \"data2\", \"read\"}, {\"data2_admin\", \"data2\", \"write\"}}, \"data2_admin\", \"data2\")\n\t// Note: \"\" (empty string) in fieldValues means matching all values.\n\ttestGetFilteredPolicy(t, e, 0, [][]string{{\"data2_admin\", \"data2\", \"read\"}}, \"data2_admin\", \"\", \"read\")\n\ttestGetFilteredPolicy(t, e, 1, [][]string{{\"bob\", \"data2\", \"write\"}, {\"data2_admin\", \"data2\", \"write\"}}, \"data2\", \"write\")\n\n\ttestHasPolicy(t, e, []string{\"alice\", \"data1\", \"read\"}, true)\n\ttestHasPolicy(t, e, []string{\"bob\", \"data2\", \"write\"}, true)\n\ttestHasPolicy(t, e, []string{\"alice\", \"data2\", \"read\"}, false)\n\ttestHasPolicy(t, e, []string{\"bob\", \"data3\", \"write\"}, false)\n\n\ttestGetGroupingPolicy(t, e, [][]string{{\"alice\", \"data2_admin\"}})\n\n\ttestGetFilteredGroupingPolicy(t, e, 0, [][]string{{\"alice\", \"data2_admin\"}}, \"alice\")\n\ttestGetFilteredGroupingPolicy(t, e, 0, [][]string{}, \"bob\")\n\ttestGetFilteredGroupingPolicy(t, e, 1, [][]string{}, \"data1_admin\")\n\ttestGetFilteredGroupingPolicy(t, e, 1, [][]string{{\"alice\", \"data2_admin\"}}, \"data2_admin\")\n\t// Note: \"\" (empty string) in fieldValues means matching all values.\n\ttestGetFilteredGroupingPolicy(t, e, 0, [][]string{{\"alice\", \"data2_admin\"}}, \"\", \"data2_admin\")\n\n\ttestHasGroupingPolicy(t, e, []string{\"alice\", \"data2_admin\"}, true)\n\ttestHasGroupingPolicy(t, e, []string{\"bob\", \"data2_admin\"}, false)\n}\n\nfunc TestModifyPolicyAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestGetPolicy(t, e, [][]string{\n\t\t{\"alice\", \"data1\", \"read\"},\n\t\t{\"bob\", \"data2\", \"write\"},\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"}})\n\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\t_, _ = e.RemovePolicy(\"bob\", \"data2\", \"write\")\n\t_, _ = e.RemovePolicy(\"alice\", \"data1\", \"read\")\n\t_, _ = e.AddPolicy(\"eve\", \"data3\", \"read\")\n\t_, _ = e.AddPolicy(\"eve\", \"data3\", \"read\")\n\n\trules := [][]string{\n\t\t{\"jack\", \"data4\", \"read\"},\n\t\t{\"jack\", \"data4\", \"read\"},\n\t\t{\"jack\", \"data4\", \"read\"},\n\t\t{\"katy\", \"data4\", \"write\"},\n\t\t{\"leyo\", \"data4\", \"read\"},\n\t\t{\"katy\", \"data4\", \"write\"},\n\t\t{\"katy\", \"data4\", \"write\"},\n\t\t{\"ham\", \"data4\", \"write\"},\n\t}\n\n\t_, _ = e.AddPolicies(rules)\n\t_, _ = e.AddPolicies(rules)\n\n\ttestGetPolicy(t, e, [][]string{\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"},\n\t\t{\"eve\", \"data3\", \"read\"},\n\t\t{\"jack\", \"data4\", \"read\"},\n\t\t{\"katy\", \"data4\", \"write\"},\n\t\t{\"leyo\", \"data4\", \"read\"},\n\t\t{\"ham\", \"data4\", \"write\"}})\n\n\t_, _ = e.RemovePolicies(rules)\n\t_, _ = e.RemovePolicies(rules)\n\n\tnamedPolicy := []string{\"eve\", \"data3\", \"read\"}\n\t_, _ = e.RemoveNamedPolicy(\"p\", namedPolicy)\n\t_, _ = e.AddNamedPolicy(\"p\", namedPolicy)\n\n\ttestGetPolicy(t, e, [][]string{\n\t\t{\"data2_admin\", \"data2\", \"read\"},\n\t\t{\"data2_admin\", \"data2\", \"write\"},\n\t\t{\"eve\", \"data3\", \"read\"}})\n\n\t_, _ = e.RemoveFilteredPolicy(1, \"data2\")\n\n\ttestGetPolicy(t, e, [][]string{{\"eve\", \"data3\", \"read\"}})\n\n\t_, _ = e.UpdatePolicy([]string{\"eve\", \"data3\", \"read\"}, []string{\"eve\", \"data3\", \"write\"})\n\n\ttestGetPolicy(t, e, [][]string{{\"eve\", \"data3\", \"write\"}})\n\n\t// This test shows a rollback effect.\n\t// _, _ = e.UpdatePolicies([][]string{{\"eve\", \"data3\", \"write\"}, {\"jack\", \"data4\", \"read\"}}, [][]string{{\"eve\", \"data3\", \"read\"}, {\"jack\", \"data4\", \"write\"}})\n\t// testGetPolicy(t, e, [][]string{{\"eve\", \"data3\", \"read\"}, {\"jack\", \"data4\", \"write\"}})\n\n\t_, _ = e.AddPolicies(rules)\n\t_, _ = e.UpdatePolicies([][]string{{\"eve\", \"data3\", \"write\"}, {\"leyo\", \"data4\", \"read\"}, {\"katy\", \"data4\", \"write\"}},\n\t\t[][]string{{\"eve\", \"data3\", \"read\"}, {\"leyo\", \"data4\", \"write\"}, {\"katy\", \"data1\", \"write\"}})\n\ttestGetPolicy(t, e, [][]string{{\"eve\", \"data3\", \"read\"}, {\"jack\", \"data4\", \"read\"}, {\"katy\", \"data1\", \"write\"}, {\"leyo\", \"data4\", \"write\"}, {\"ham\", \"data4\", \"write\"}})\n\n\te.ClearPolicy()\n\t_, _ = e.AddPoliciesEx([][]string{{\"user1\", \"data1\", \"read\"}, {\"user1\", \"data1\", \"read\"}})\n\ttestGetPolicy(t, e, [][]string{{\"user1\", \"data1\", \"read\"}})\n\t// {\"user1\", \"data1\", \"read\"} repeated\n\t_, _ = e.AddPoliciesEx([][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\ttestGetPolicy(t, e, [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}})\n\t// {\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"} repeated\n\t_, _ = e.AddNamedPoliciesEx(\"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}})\n\ttestGetPolicy(t, e, [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}})\n\t// {\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}, , {\"user3\", \"data3\", \"read\"} repeated\n\t_, _ = e.SelfAddPoliciesEx(\"p\", \"p\", [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}})\n\ttestGetPolicy(t, e, [][]string{{\"user1\", \"data1\", \"read\"}, {\"user2\", \"data2\", \"read\"}, {\"user3\", \"data3\", \"read\"}, {\"user4\", \"data4\", \"read\"}})\n}\n\nfunc TestModifyGroupingPolicyAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestGetRoles(t, e, []string{\"data2_admin\"}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"eve\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\")\n\n\t_, _ = e.RemoveGroupingPolicy(\"alice\", \"data2_admin\")\n\t_, _ = e.AddGroupingPolicy(\"bob\", \"data1_admin\")\n\t_, _ = e.AddGroupingPolicy(\"eve\", \"data3_admin\")\n\n\tgroupingRules := [][]string{\n\t\t{\"ham\", \"data4_admin\"},\n\t\t{\"jack\", \"data5_admin\"},\n\t}\n\n\t_, _ = e.AddGroupingPolicies(groupingRules)\n\ttestGetRoles(t, e, []string{\"data4_admin\"}, \"ham\")\n\ttestGetRoles(t, e, []string{\"data5_admin\"}, \"jack\")\n\t_, _ = e.RemoveGroupingPolicies(groupingRules)\n\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\tnamedGroupingPolicy := []string{\"alice\", \"data2_admin\"}\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\t_, _ = e.AddNamedGroupingPolicy(\"g\", namedGroupingPolicy)\n\ttestGetRoles(t, e, []string{\"data2_admin\"}, \"alice\")\n\t_, _ = e.RemoveNamedGroupingPolicy(\"g\", namedGroupingPolicy)\n\n\t_, _ = e.AddNamedGroupingPolicies(\"g\", groupingRules)\n\t_, _ = e.AddNamedGroupingPolicies(\"g\", groupingRules)\n\ttestGetRoles(t, e, []string{\"data4_admin\"}, \"ham\")\n\ttestGetRoles(t, e, []string{\"data5_admin\"}, \"jack\")\n\t_, _ = e.RemoveNamedGroupingPolicies(\"g\", groupingRules)\n\t_, _ = e.RemoveNamedGroupingPolicies(\"g\", groupingRules)\n\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\ttestGetRoles(t, e, []string{\"data1_admin\"}, \"bob\")\n\ttestGetRoles(t, e, []string{\"data3_admin\"}, \"eve\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\")\n\n\ttestGetUsers(t, e, []string{\"bob\"}, \"data1_admin\")\n\ttestGetUsers(t, e, []string{}, \"data2_admin\")\n\ttestGetUsers(t, e, []string{\"eve\"}, \"data3_admin\")\n\n\t_, _ = e.RemoveFilteredGroupingPolicy(0, \"bob\")\n\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{\"data3_admin\"}, \"eve\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\")\n\n\ttestGetUsers(t, e, []string{}, \"data1_admin\")\n\ttestGetUsers(t, e, []string{}, \"data2_admin\")\n\ttestGetUsers(t, e, []string{\"eve\"}, \"data3_admin\")\n\t_, _ = e.AddGroupingPolicy(\"data3_admin\", \"data4_admin\")\n\t_, _ = e.UpdateGroupingPolicy([]string{\"eve\", \"data3_admin\"}, []string{\"eve\", \"admin\"})\n\t_, _ = e.UpdateGroupingPolicy([]string{\"data3_admin\", \"data4_admin\"}, []string{\"admin\", \"data4_admin\"})\n\ttestGetUsers(t, e, []string{\"admin\"}, \"data4_admin\")\n\ttestGetUsers(t, e, []string{\"eve\"}, \"admin\")\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"eve\")\n\ttestGetRoles(t, e, []string{\"data4_admin\"}, \"admin\")\n\n\t_, _ = e.UpdateGroupingPolicies([][]string{{\"eve\", \"admin\"}}, [][]string{{\"eve\", \"admin_groups\"}})\n\t_, _ = e.UpdateGroupingPolicies([][]string{{\"admin\", \"data4_admin\"}}, [][]string{{\"admin\", \"data5_admin\"}})\n\ttestGetUsers(t, e, []string{\"admin\"}, \"data5_admin\")\n\ttestGetUsers(t, e, []string{\"eve\"}, \"admin_groups\")\n\n\ttestGetRoles(t, e, []string{\"data5_admin\"}, \"admin\")\n\ttestGetRoles(t, e, []string{\"admin_groups\"}, \"eve\")\n\n\te.ClearPolicy()\n\t_, _ = e.AddGroupingPoliciesEx([][]string{{\"user1\", \"member\"}})\n\ttestGetUsers(t, e, []string{\"user1\"}, \"member\")\n\t// {\"user1\", \"member\"} repeated\n\t_, _ = e.AddGroupingPoliciesEx([][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}})\n\ttestGetUsers(t, e, []string{\"user1\", \"user2\"}, \"member\")\n\t// {\"user1\", \"member\"}, {\"user2\", \"member\"} repeated\n\t_, _ = e.AddNamedGroupingPoliciesEx(\"g\", [][]string{{\"user1\", \"member\"}, {\"user2\", \"member\"}, {\"user3\", \"member\"}})\n\ttestGetUsers(t, e, []string{\"user1\", \"user2\", \"user3\"}, \"member\")\n}\n"
  },
  {
    "path": "model/assertion.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n)\n\n// Assertion represents an expression in a section of the model.\n// For example: r = sub, obj, act.\ntype Assertion struct {\n\tKey             string\n\tValue           string\n\tTokens          []string\n\tParamsTokens    []string\n\tPolicy          [][]string\n\tPolicyMap       map[string]int\n\tRM              rbac.RoleManager\n\tCondRM          rbac.ConditionalRoleManager\n\tFieldIndexMap   map[string]int\n\tFieldIndexMutex sync.RWMutex\n}\n\nfunc (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, rules [][]string) error {\n\tast.RM = rm\n\tcount := strings.Count(ast.Value, \"_\")\n\tif count < 2 {\n\t\treturn errors.New(\"the number of \\\"_\\\" in role definition should be at least 2\")\n\t}\n\n\tfor _, rule := range rules {\n\t\tif len(rule) < count {\n\t\t\treturn errors.New(\"grouping policy elements do not meet role definition\")\n\t\t}\n\t\tif len(rule) > count {\n\t\t\trule = rule[:count]\n\t\t}\n\t\tswitch op {\n\t\tcase PolicyAdd:\n\t\t\terr := rm.AddLink(rule[0], rule[1], rule[2:]...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase PolicyRemove:\n\t\t\terr := rm.DeleteLink(rule[0], rule[1], rule[2:]...)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error {\n\tast.RM = rm\n\tcount := strings.Count(ast.Value, \"_\")\n\tif count < 2 {\n\t\treturn errors.New(\"the number of \\\"_\\\" in role definition should be at least 2\")\n\t}\n\tfor _, rule := range ast.Policy {\n\t\tif len(rule) < count {\n\t\t\treturn errors.New(\"grouping policy elements do not meet role definition\")\n\t\t}\n\t\tif len(rule) > count {\n\t\t\trule = rule[:count]\n\t\t}\n\t\terr := ast.RM.AddLink(rule[0], rule[1], rule[2:]...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (ast *Assertion) buildIncrementalConditionalRoleLinks(condRM rbac.ConditionalRoleManager, op PolicyOp, rules [][]string) error {\n\tast.CondRM = condRM\n\tcount := strings.Count(ast.Value, \"_\")\n\tif count < 2 {\n\t\treturn errors.New(\"the number of \\\"_\\\" in role definition should be at least 2\")\n\t}\n\n\tfor _, rule := range rules {\n\t\tif len(rule) < count {\n\t\t\treturn errors.New(\"grouping policy elements do not meet role definition\")\n\t\t}\n\t\tif len(rule) > count {\n\t\t\trule = rule[:count]\n\t\t}\n\n\t\tvar err error\n\t\tdomainRule := rule[2:len(ast.Tokens)]\n\n\t\tswitch op {\n\t\tcase PolicyAdd:\n\t\t\terr = ast.addConditionalRoleLink(rule, domainRule)\n\t\tcase PolicyRemove:\n\t\t\terr = ast.CondRM.DeleteLink(rule[0], rule[1], rule[2:]...)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (ast *Assertion) buildConditionalRoleLinks(condRM rbac.ConditionalRoleManager) error {\n\tast.CondRM = condRM\n\tcount := strings.Count(ast.Value, \"_\")\n\tif count < 2 {\n\t\treturn errors.New(\"the number of \\\"_\\\" in role definition should be at least 2\")\n\t}\n\tfor _, rule := range ast.Policy {\n\t\tif len(rule) < count {\n\t\t\treturn errors.New(\"grouping policy elements do not meet role definition\")\n\t\t}\n\t\tif len(rule) > count {\n\t\t\trule = rule[:count]\n\t\t}\n\n\t\tdomainRule := rule[2:len(ast.Tokens)]\n\n\t\terr := ast.addConditionalRoleLink(rule, domainRule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// addConditionalRoleLink adds Link to rbac.ConditionalRoleManager and sets the parameters for LinkConditionFunc.\nfunc (ast *Assertion) addConditionalRoleLink(rule []string, domainRule []string) error {\n\tvar err error\n\tif len(domainRule) == 0 {\n\t\terr = ast.CondRM.AddLink(rule[0], rule[1])\n\t\tif err == nil {\n\t\t\tast.CondRM.SetLinkConditionFuncParams(rule[0], rule[1], rule[len(ast.Tokens):]...)\n\t\t}\n\t} else {\n\t\tdomain := domainRule[0]\n\t\terr = ast.CondRM.AddLink(rule[0], rule[1], domain)\n\t\tif err == nil {\n\t\t\tast.CondRM.SetDomainLinkConditionFuncParams(rule[0], rule[1], domain, rule[len(ast.Tokens):]...)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (ast *Assertion) copy() *Assertion {\n\ttokens := append([]string(nil), ast.Tokens...)\n\tpolicy := make([][]string, len(ast.Policy))\n\n\tfor i, p := range ast.Policy {\n\t\tpolicy[i] = append(policy[i], p...)\n\t}\n\tpolicyMap := make(map[string]int)\n\tfor k, v := range ast.PolicyMap {\n\t\tpolicyMap[k] = v\n\t}\n\n\tast.FieldIndexMutex.RLock()\n\tfieldIndexMap := make(map[string]int)\n\tfor k, v := range ast.FieldIndexMap {\n\t\tfieldIndexMap[k] = v\n\t}\n\tast.FieldIndexMutex.RUnlock()\n\n\tnewAst := &Assertion{\n\t\tKey:           ast.Key,\n\t\tValue:         ast.Value,\n\t\tPolicyMap:     policyMap,\n\t\tTokens:        tokens,\n\t\tPolicy:        policy,\n\t\tFieldIndexMap: fieldIndexMap,\n\t\tParamsTokens:  append([]string(nil), ast.ParamsTokens...),\n\t\tRM:            ast.RM,\n\t\tCondRM:        ast.CondRM,\n\t}\n\n\treturn newAst\n}\n"
  },
  {
    "path": "model/constraint.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/errors\"\n)\n\n// ConstraintType represents the type of constraint.\ntype ConstraintType int\n\nconst (\n\tConstraintTypeSOD ConstraintType = iota\n\tConstraintTypeSODMax\n\tConstraintTypeRoleMax\n\tConstraintTypeRolePre\n)\n\n// Constraint represents a policy constraint.\ntype Constraint struct {\n\tKey        string\n\tType       ConstraintType\n\tRoles      []string\n\tRole       string\n\tMaxCount   int\n\tPreReqRole string\n}\n\nvar (\n\t// Regex patterns for parsing constraints (compiled once at package initialization).\n\tsodPattern     = regexp.MustCompile(`^sod\\s*\\(\\s*\"([^\"]+)\"\\s*,\\s*\"([^\"]+)\"\\s*\\)$`)\n\tsodMaxPattern  = regexp.MustCompile(`^sodMax\\s*\\(\\s*\\[([^\\]]+)\\]\\s*,\\s*(\\d+)\\s*\\)$`)\n\troleMaxPattern = regexp.MustCompile(`^roleMax\\s*\\(\\s*\"([^\"]+)\"\\s*,\\s*(\\d+)\\s*\\)$`)\n\trolePrePattern = regexp.MustCompile(`^rolePre\\s*\\(\\s*\"([^\"]+)\"\\s*,\\s*\"([^\"]+)\"\\s*\\)$`)\n)\n\n// parseRolesArray parses a comma-separated string of quoted role names.\nfunc parseRolesArray(rolesStr string) ([]string, error) {\n\tvar roles []string\n\tfor _, role := range strings.Split(rolesStr, \",\") {\n\t\trole = strings.TrimSpace(role)\n\t\trole = strings.Trim(role, `\"`)\n\t\tif role != \"\" {\n\t\t\troles = append(roles, role)\n\t\t}\n\t}\n\n\tif len(roles) == 0 {\n\t\treturn nil, fmt.Errorf(\"no roles found in role array\")\n\t}\n\n\treturn roles, nil\n}\n\n// parseConstraint parses a constraint definition string.\nfunc parseConstraint(key, value string) (*Constraint, error) {\n\tvalue = strings.TrimSpace(value)\n\n\t// Try to match sod pattern\n\tif matches := sodPattern.FindStringSubmatch(value); matches != nil {\n\t\treturn &Constraint{\n\t\t\tKey:   key,\n\t\t\tType:  ConstraintTypeSOD,\n\t\t\tRoles: []string{matches[1], matches[2]},\n\t\t}, nil\n\t}\n\n\t// Try to match sodMax pattern\n\tif matches := sodMaxPattern.FindStringSubmatch(value); matches != nil {\n\t\tmaxCount, err := strconv.Atoi(matches[2])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid max count in sodMax: %w\", err)\n\t\t}\n\n\t\troles, err := parseRolesArray(matches[1])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"sodMax: %w\", err)\n\t\t}\n\n\t\treturn &Constraint{\n\t\t\tKey:      key,\n\t\t\tType:     ConstraintTypeSODMax,\n\t\t\tRoles:    roles,\n\t\t\tMaxCount: maxCount,\n\t\t}, nil\n\t}\n\n\t// Try to match roleMax pattern\n\tif matches := roleMaxPattern.FindStringSubmatch(value); matches != nil {\n\t\tmaxCount, err := strconv.Atoi(matches[2])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid max count in roleMax: %w\", err)\n\t\t}\n\t\treturn &Constraint{\n\t\t\tKey:      key,\n\t\t\tType:     ConstraintTypeRoleMax,\n\t\t\tRole:     matches[1],\n\t\t\tMaxCount: maxCount,\n\t\t}, nil\n\t}\n\n\t// Try to match rolePre pattern\n\tif matches := rolePrePattern.FindStringSubmatch(value); matches != nil {\n\t\treturn &Constraint{\n\t\t\tKey:        key,\n\t\t\tType:       ConstraintTypeRolePre,\n\t\t\tRole:       matches[1],\n\t\t\tPreReqRole: matches[2],\n\t\t}, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"unrecognized constraint format: %s\", value)\n}\n\n// ValidateConstraints validates all constraints against the current policy.\nfunc (model Model) ValidateConstraints() error {\n\t// Check if constraints exist\n\tif model[\"c\"] == nil || len(model[\"c\"]) == 0 {\n\t\treturn nil // No constraints to validate\n\t}\n\n\t// Check if RBAC is enabled\n\tif model[\"g\"] == nil || len(model[\"g\"]) == 0 {\n\t\treturn errors.ErrConstraintRequiresRBAC\n\t}\n\n\t// Get grouping policy\n\tgAssertion := model[\"g\"][\"g\"]\n\tif gAssertion == nil {\n\t\treturn errors.ErrConstraintRequiresRBAC\n\t}\n\n\t// Validate each constraint\n\tfor _, assertion := range model[\"c\"] {\n\t\tconstraint, err := parseConstraint(assertion.Key, assertion.Value)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %s\", errors.ErrConstraintParsingError, err.Error())\n\t\t}\n\n\t\tif err := model.validateConstraint(constraint, gAssertion.Policy); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// validateConstraint validates a single constraint against the policy.\nfunc (model Model) validateConstraint(constraint *Constraint, groupingPolicy [][]string) error {\n\tswitch constraint.Type {\n\tcase ConstraintTypeSOD:\n\t\treturn model.validateSOD(constraint, groupingPolicy)\n\tcase ConstraintTypeSODMax:\n\t\treturn model.validateSODMax(constraint, groupingPolicy)\n\tcase ConstraintTypeRoleMax:\n\t\treturn model.validateRoleMax(constraint, groupingPolicy)\n\tcase ConstraintTypeRolePre:\n\t\treturn model.validateRolePre(constraint, groupingPolicy)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown constraint type\")\n\t}\n}\n\n// buildUserRoleMap builds a map of users to their assigned roles from grouping policy.\nfunc buildUserRoleMap(groupingPolicy [][]string) map[string]map[string]bool {\n\tuserRoles := make(map[string]map[string]bool)\n\n\tfor _, rule := range groupingPolicy {\n\t\tif len(rule) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tuser := rule[0]\n\t\trole := rule[1]\n\n\t\tif userRoles[user] == nil {\n\t\t\tuserRoles[user] = make(map[string]bool)\n\t\t}\n\t\tuserRoles[user][role] = true\n\t}\n\n\treturn userRoles\n}\n\n// validateSOD validates a Separation of Duties constraint.\nfunc (model Model) validateSOD(constraint *Constraint, groupingPolicy [][]string) error {\n\tif len(constraint.Roles) != 2 {\n\t\treturn errors.NewConstraintViolationError(constraint.Key, \"sod requires exactly 2 roles\")\n\t}\n\n\trole1, role2 := constraint.Roles[0], constraint.Roles[1]\n\tuserRoles := buildUserRoleMap(groupingPolicy)\n\n\t// Check if any user has both roles\n\tfor user, roles := range userRoles {\n\t\tif roles[role1] && roles[role2] {\n\t\t\treturn errors.NewConstraintViolationError(constraint.Key,\n\t\t\t\tfmt.Sprintf(\"user '%s' cannot have both roles '%s' and '%s'\", user, role1, role2))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// validateSODMax validates a maximum role count constraint for a role set.\nfunc (model Model) validateSODMax(constraint *Constraint, groupingPolicy [][]string) error {\n\tuserRoles := buildUserRoleMap(groupingPolicy)\n\n\t// Check if any user has more than maxCount roles from the role set\n\tfor user, roles := range userRoles {\n\t\tcount := 0\n\t\tfor _, role := range constraint.Roles {\n\t\t\tif roles[role] {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\tif count > constraint.MaxCount {\n\t\t\treturn errors.NewConstraintViolationError(constraint.Key,\n\t\t\t\tfmt.Sprintf(\"user '%s' has %d roles from %v, exceeds maximum of %d\",\n\t\t\t\t\tuser, count, constraint.Roles, constraint.MaxCount))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// validateRoleMax validates a role cardinality constraint.\nfunc (model Model) validateRoleMax(constraint *Constraint, groupingPolicy [][]string) error {\n\troleCount := 0\n\n\t// Count how many users have this role\n\tfor _, rule := range groupingPolicy {\n\t\tif len(rule) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\trole := rule[1]\n\n\t\tif role == constraint.Role {\n\t\t\troleCount++\n\t\t}\n\t}\n\n\tif roleCount > constraint.MaxCount {\n\t\treturn errors.NewConstraintViolationError(constraint.Key,\n\t\t\tfmt.Sprintf(\"role '%s' assigned to %d users, exceeds maximum of %d\",\n\t\t\t\tconstraint.Role, roleCount, constraint.MaxCount))\n\t}\n\n\treturn nil\n}\n\n// validateRolePre validates a prerequisite role constraint.\nfunc (model Model) validateRolePre(constraint *Constraint, groupingPolicy [][]string) error {\n\tuserRoles := buildUserRoleMap(groupingPolicy)\n\n\t// Check if any user has the main role without the prerequisite role\n\tfor user, roles := range userRoles {\n\t\tif roles[constraint.Role] && !roles[constraint.PreReqRole] {\n\t\t\treturn errors.NewConstraintViolationError(constraint.Key,\n\t\t\t\tfmt.Sprintf(\"user '%s' has role '%s' but lacks prerequisite role '%s'\",\n\t\t\t\t\tuser, constraint.Role, constraint.PreReqRole))\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "model/function.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n\t\"github.com/casbin/govaluate\"\n)\n\n// FunctionMap represents the collection of Function.\ntype FunctionMap struct {\n\tfns *sync.Map\n}\n\n// [string]govaluate.ExpressionFunction\n\n// AddFunction adds an expression function.\nfunc (fm *FunctionMap) AddFunction(name string, function govaluate.ExpressionFunction) {\n\tfm.fns.LoadOrStore(name, function)\n}\n\n// LoadFunctionMap loads an initial function map.\nfunc LoadFunctionMap() FunctionMap {\n\tfm := &FunctionMap{}\n\tfm.fns = &sync.Map{}\n\n\tfm.AddFunction(\"keyMatch\", util.KeyMatchFunc)\n\tfm.AddFunction(\"keyGet\", util.KeyGetFunc)\n\tfm.AddFunction(\"keyMatch2\", util.KeyMatch2Func)\n\tfm.AddFunction(\"keyGet2\", util.KeyGet2Func)\n\tfm.AddFunction(\"keyMatch3\", util.KeyMatch3Func)\n\tfm.AddFunction(\"keyGet3\", util.KeyGet3Func)\n\tfm.AddFunction(\"keyMatch4\", util.KeyMatch4Func)\n\tfm.AddFunction(\"keyMatch5\", util.KeyMatch5Func)\n\tfm.AddFunction(\"regexMatch\", util.RegexMatchFunc)\n\tfm.AddFunction(\"ipMatch\", util.IPMatchFunc)\n\tfm.AddFunction(\"globMatch\", util.GlobMatchFunc)\n\n\treturn *fm\n}\n\n// GetFunctions return a map with all the functions.\nfunc (fm *FunctionMap) GetFunctions() map[string]govaluate.ExpressionFunction {\n\tret := make(map[string]govaluate.ExpressionFunction)\n\n\tfm.fns.Range(func(k interface{}, v interface{}) bool {\n\t\tret[k.(string)] = v.(govaluate.ExpressionFunction)\n\t\treturn true\n\t})\n\n\treturn ret\n}\n"
  },
  {
    "path": "model/model.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"container/list\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/config\"\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// Model represents the whole access control model.\ntype Model map[string]AssertionMap\n\n// AssertionMap is the collection of assertions, can be \"r\", \"p\", \"g\", \"e\", \"m\".\ntype AssertionMap map[string]*Assertion\n\nconst defaultDomain string = \"\"\nconst defaultSeparator = \"::\"\n\nvar sectionNameMap = map[string]string{\n\t\"r\": \"request_definition\",\n\t\"p\": \"policy_definition\",\n\t\"g\": \"role_definition\",\n\t\"e\": \"policy_effect\",\n\t\"m\": \"matchers\",\n\t\"c\": \"constraint_definition\",\n}\n\n// Minimal required sections for a model to be valid.\nvar requiredSections = []string{\"r\", \"p\", \"e\", \"m\"}\n\nfunc loadAssertion(model Model, cfg config.ConfigInterface, sec string, key string) bool {\n\tvalue := cfg.String(sectionNameMap[sec] + \"::\" + key)\n\treturn model.AddDef(sec, key, value)\n}\n\nvar paramsRegex = regexp.MustCompile(`\\((.*?)\\)`)\n\n// getParamsToken Get ParamsToken from Assertion.Value.\nfunc getParamsToken(value string) []string {\n\tparamsString := paramsRegex.FindString(value)\n\tif paramsString == \"\" {\n\t\treturn nil\n\t}\n\tparamsString = strings.TrimSuffix(strings.TrimPrefix(paramsString, \"(\"), \")\")\n\treturn strings.Split(paramsString, \",\")\n}\n\n// AddDef adds an assertion to the model.\nfunc (model Model) AddDef(sec string, key string, value string) bool {\n\tif value == \"\" {\n\t\treturn false\n\t}\n\n\tast := Assertion{}\n\tast.Key = key\n\tast.Value = value\n\tast.PolicyMap = make(map[string]int)\n\tast.FieldIndexMap = make(map[string]int)\n\n\tif sec == \"r\" || sec == \"p\" {\n\t\tast.Tokens = strings.Split(ast.Value, \",\")\n\t\tfor i := range ast.Tokens {\n\t\t\tast.Tokens[i] = key + \"_\" + strings.TrimSpace(ast.Tokens[i])\n\t\t}\n\t} else if sec == \"g\" {\n\t\tast.ParamsTokens = getParamsToken(ast.Value)\n\t\tast.Tokens = strings.Split(ast.Value, \",\")\n\t\tast.Tokens = ast.Tokens[:len(ast.Tokens)-len(ast.ParamsTokens)]\n\t} else {\n\t\tast.Value = util.RemoveComments(util.EscapeAssertion(ast.Value))\n\t}\n\n\tif sec == \"m\" {\n\t\t// Escape backslashes in string literals to match CSV parsing behavior\n\t\tast.Value = util.EscapeStringLiterals(ast.Value)\n\n\t\tif strings.Contains(ast.Value, \"in\") {\n\t\t\tast.Value = strings.Replace(strings.Replace(ast.Value, \"[\", \"(\", -1), \"]\", \")\", -1)\n\t\t}\n\t}\n\n\t_, ok := model[sec]\n\tif !ok {\n\t\tmodel[sec] = make(AssertionMap)\n\t}\n\n\tmodel[sec][key] = &ast\n\treturn true\n}\n\nfunc getKeySuffix(i int) string {\n\tif i == 1 {\n\t\treturn \"\"\n\t}\n\n\treturn strconv.Itoa(i)\n}\n\nfunc loadSection(model Model, cfg config.ConfigInterface, sec string) {\n\ti := 1\n\tfor {\n\t\tif !loadAssertion(model, cfg, sec, sec+getKeySuffix(i)) {\n\t\t\tbreak\n\t\t} else {\n\t\t\ti++\n\t\t}\n\t}\n}\n\n// NewModel creates an empty model.\nfunc NewModel() Model {\n\tm := make(Model)\n\n\treturn m\n}\n\n// NewModelFromFile creates a model from a .CONF file.\nfunc NewModelFromFile(path string) (Model, error) {\n\tm := NewModel()\n\n\terr := m.LoadModel(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn m, nil\n}\n\n// NewModelFromString creates a model from a string which contains model text.\nfunc NewModelFromString(text string) (Model, error) {\n\tm := NewModel()\n\n\terr := m.LoadModelFromText(text)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn m, nil\n}\n\n// LoadModel loads the model from model CONF file.\nfunc (model Model) LoadModel(path string) error {\n\tcfg, err := config.NewConfig(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn model.loadModelFromConfig(cfg)\n}\n\n// LoadModelFromText loads the model from the text.\nfunc (model Model) LoadModelFromText(text string) error {\n\tcfg, err := config.NewConfigFromText(text)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn model.loadModelFromConfig(cfg)\n}\n\n// loadModelFromConfig loads the model from a config interface.\n// It loads all sections defined in sectionNameMap and validates that required sections are present.\n// If constraint_definition section exists, it validates all constraints against the current policy.\n// Note: Constraint validation is performed during model loading, which may affect loading performance\n// and can cause model loading to fail if constraints are violated or invalid.\nfunc (model Model) loadModelFromConfig(cfg config.ConfigInterface) error {\n\tfor s := range sectionNameMap {\n\t\tloadSection(model, cfg, s)\n\t}\n\tms := make([]string, 0)\n\tfor _, rs := range requiredSections {\n\t\tif !model.hasSection(rs) {\n\t\t\tms = append(ms, sectionNameMap[rs])\n\t\t}\n\t}\n\tif len(ms) > 0 {\n\t\treturn fmt.Errorf(\"missing required sections: %s\", strings.Join(ms, \",\"))\n\t}\n\n\t// Validate constraints after model is loaded\n\tif err := model.ValidateConstraints(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (model Model) hasSection(sec string) bool {\n\tsection := model[sec]\n\treturn section != nil\n}\n\nfunc (model Model) GetAssertion(sec string, ptype string) (*Assertion, error) {\n\tif model[sec] == nil {\n\t\treturn nil, fmt.Errorf(\"missing required section %s\", sec)\n\t}\n\tif model[sec][ptype] == nil {\n\t\treturn nil, fmt.Errorf(\"missing required definition %s in section %s\", ptype, sec)\n\t}\n\treturn model[sec][ptype], nil\n}\n\n// PrintModel prints the model to the log.\nfunc (model Model) PrintModel() {\n\t// Logger has been removed - this is now a no-op\n}\n\nfunc (model Model) SortPoliciesBySubjectHierarchy() error {\n\tif model[\"e\"][\"e\"].Value != constant.SubjectPriorityEffect {\n\t\treturn nil\n\t}\n\tg, err := model.GetAssertion(\"g\", \"g\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tsubIndex := 0\n\tfor ptype, assertion := range model[\"p\"] {\n\t\tdomainIndex, err := model.GetFieldIndex(ptype, constant.DomainIndex)\n\t\tif err != nil {\n\t\t\tdomainIndex = -1\n\t\t}\n\t\tpolicies := assertion.Policy\n\t\tsubjectHierarchyMap, err := getSubjectHierarchyMap(g.Policy)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsort.SliceStable(policies, func(i, j int) bool {\n\t\t\tdomain1, domain2 := defaultDomain, defaultDomain\n\t\t\tif domainIndex != -1 {\n\t\t\t\tdomain1 = policies[i][domainIndex]\n\t\t\t\tdomain2 = policies[j][domainIndex]\n\t\t\t}\n\t\t\tname1, name2 := getNameWithDomain(domain1, policies[i][subIndex]), getNameWithDomain(domain2, policies[j][subIndex])\n\t\t\tp1 := subjectHierarchyMap[name1]\n\t\t\tp2 := subjectHierarchyMap[name2]\n\t\t\treturn p1 > p2\n\t\t})\n\t\tfor i, policy := range assertion.Policy {\n\t\t\tassertion.PolicyMap[strings.Join(policy, \",\")] = i\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getSubjectHierarchyMap(policies [][]string) (map[string]int, error) {\n\tsubjectHierarchyMap := make(map[string]int)\n\t// Tree structure of role\n\tpolicyMap := make(map[string][]string)\n\tfor _, policy := range policies {\n\t\tif len(policy) < 2 {\n\t\t\treturn nil, errors.New(\"policy g expect 2 more params\")\n\t\t}\n\t\tdomain := defaultDomain\n\t\tif len(policy) != 2 {\n\t\t\tdomain = policy[2]\n\t\t}\n\t\tchild := getNameWithDomain(domain, policy[0])\n\t\tparent := getNameWithDomain(domain, policy[1])\n\t\tpolicyMap[parent] = append(policyMap[parent], child)\n\t\tif _, ok := subjectHierarchyMap[child]; !ok {\n\t\t\tsubjectHierarchyMap[child] = 0\n\t\t}\n\t\tif _, ok := subjectHierarchyMap[parent]; !ok {\n\t\t\tsubjectHierarchyMap[parent] = 0\n\t\t}\n\t\tsubjectHierarchyMap[child] = 1\n\t}\n\t// Use queues for levelOrder\n\tqueue := list.New()\n\tfor k, v := range subjectHierarchyMap {\n\t\troot := k\n\t\tif v != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tlv := 0\n\t\tqueue.PushBack(root)\n\t\tfor queue.Len() != 0 {\n\t\t\tsz := queue.Len()\n\t\t\tfor i := 0; i < sz; i++ {\n\t\t\t\tnode := queue.Front()\n\t\t\t\tqueue.Remove(node)\n\t\t\t\tnodeValue := node.Value.(string)\n\t\t\t\tsubjectHierarchyMap[nodeValue] = lv\n\t\t\t\tif _, ok := policyMap[nodeValue]; ok {\n\t\t\t\t\tfor _, child := range policyMap[nodeValue] {\n\t\t\t\t\t\tqueue.PushBack(child)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlv++\n\t\t}\n\t}\n\treturn subjectHierarchyMap, nil\n}\n\nfunc getNameWithDomain(domain string, name string) string {\n\treturn domain + defaultSeparator + name\n}\n\nfunc (model Model) SortPoliciesByPriority() error {\n\tfor ptype, assertion := range model[\"p\"] {\n\t\tpriorityIndex, err := model.GetFieldIndex(ptype, constant.PriorityIndex)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tpolicies := assertion.Policy\n\t\tsort.SliceStable(policies, func(i, j int) bool {\n\t\t\tp1, err := strconv.Atoi(policies[i][priorityIndex])\n\t\t\tif err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tp2, err := strconv.Atoi(policies[j][priorityIndex])\n\t\t\tif err != nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn p1 < p2\n\t\t})\n\t\tfor i, policy := range assertion.Policy {\n\t\t\tassertion.PolicyMap[strings.Join(policy, \",\")] = i\n\t\t}\n\t}\n\treturn nil\n}\n\nvar (\n\tpPattern = regexp.MustCompile(\"^p_\")\n\trPattern = regexp.MustCompile(\"^r_\")\n)\n\nfunc (model Model) ToText() string {\n\ttokenPatterns := make(map[string]string)\n\n\tfor _, ptype := range []string{\"r\", \"p\"} {\n\t\tfor _, token := range model[ptype][ptype].Tokens {\n\t\t\ttokenPatterns[token] = rPattern.ReplaceAllString(pPattern.ReplaceAllString(token, \"p.\"), \"r.\")\n\t\t}\n\t}\n\tif strings.Contains(model[\"e\"][\"e\"].Value, \"p_eft\") {\n\t\ttokenPatterns[\"p_eft\"] = \"p.eft\"\n\t}\n\ts := strings.Builder{}\n\twriteString := func(sec string) {\n\t\tfor ptype := range model[sec] {\n\t\t\tvalue := model[sec][ptype].Value\n\t\t\tfor tokenPattern, newToken := range tokenPatterns {\n\t\t\t\tvalue = strings.Replace(value, tokenPattern, newToken, -1)\n\t\t\t}\n\t\t\ts.WriteString(fmt.Sprintf(\"%s = %s\\n\", sec, value))\n\t\t}\n\t}\n\ts.WriteString(\"[request_definition]\\n\")\n\twriteString(\"r\")\n\ts.WriteString(\"[policy_definition]\\n\")\n\twriteString(\"p\")\n\tif _, ok := model[\"g\"]; ok {\n\t\ts.WriteString(\"[role_definition]\\n\")\n\t\tfor ptype := range model[\"g\"] {\n\t\t\ts.WriteString(fmt.Sprintf(\"%s = %s\\n\", ptype, model[\"g\"][ptype].Value))\n\t\t}\n\t}\n\tif _, ok := model[\"c\"]; ok {\n\t\ts.WriteString(\"[constraint_definition]\\n\")\n\t\tfor ptype := range model[\"c\"] {\n\t\t\ts.WriteString(fmt.Sprintf(\"%s = %s\\n\", ptype, model[\"c\"][ptype].Value))\n\t\t}\n\t}\n\ts.WriteString(\"[policy_effect]\\n\")\n\twriteString(\"e\")\n\ts.WriteString(\"[matchers]\\n\")\n\twriteString(\"m\")\n\treturn s.String()\n}\n\nfunc (model Model) Copy() Model {\n\tnewModel := NewModel()\n\n\tfor sec, m := range model {\n\t\tnewAstMap := make(AssertionMap)\n\t\tfor ptype, ast := range m {\n\t\t\tnewAstMap[ptype] = ast.copy()\n\t\t}\n\t\tnewModel[sec] = newAstMap\n\t}\n\n\treturn newModel\n}\n\nfunc (model Model) GetFieldIndex(ptype string, field string) (int, error) {\n\tassertion := model[\"p\"][ptype]\n\n\tassertion.FieldIndexMutex.RLock()\n\tif index, ok := assertion.FieldIndexMap[field]; ok {\n\t\tassertion.FieldIndexMutex.RUnlock()\n\t\treturn index, nil\n\t}\n\tassertion.FieldIndexMutex.RUnlock()\n\n\tpattern := fmt.Sprintf(\"%s_\"+field, ptype)\n\tindex := -1\n\tfor i, token := range assertion.Tokens {\n\t\tif token == pattern {\n\t\t\tindex = i\n\t\t\tbreak\n\t\t}\n\t}\n\tif index == -1 {\n\t\treturn index, fmt.Errorf(field + \" index is not set, please use enforcer.SetFieldIndex() to set index\")\n\t}\n\n\tassertion.FieldIndexMutex.Lock()\n\tassertion.FieldIndexMap[field] = index\n\tassertion.FieldIndexMutex.Unlock()\n\n\treturn index, nil\n}\n"
  },
  {
    "path": "model/model_test.go",
    "content": "// Copyright 2019 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"io/ioutil\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/config\"\n\t\"github.com/casbin/casbin/v3/constant\"\n)\n\nvar (\n\tbasicExample = filepath.Join(\"..\", \"examples\", \"basic_model.conf\")\n\tbasicConfig  = &MockConfig{\n\t\tdata: map[string]string{\n\t\t\t\"request_definition::r\": \"sub, obj, act\",\n\t\t\t\"policy_definition::p\":  \"sub, obj, act\",\n\t\t\t\"policy_effect::e\":      \"some(where (p.eft == allow))\",\n\t\t\t\"matchers::m\":           \"r.sub == p.sub && r.obj == p.obj && r.act == p.act\",\n\t\t},\n\t}\n)\n\ntype MockConfig struct {\n\tdata map[string]string\n\tconfig.ConfigInterface\n}\n\nfunc (mc *MockConfig) String(key string) string {\n\treturn mc.data[key]\n}\n\nfunc TestNewModel(t *testing.T) {\n\tm := NewModel()\n\tif m == nil {\n\t\tt.Error(\"new model should not be nil\")\n\t}\n}\n\nfunc TestNewModelFromFile(t *testing.T) {\n\tm, err := NewModelFromFile(basicExample)\n\tif err != nil {\n\t\tt.Errorf(\"model failed to load from file: %s\", err)\n\t}\n\tif m == nil {\n\t\tt.Error(\"model should not be nil\")\n\t}\n}\n\nfunc TestNewModelFromString(t *testing.T) {\n\tmodelBytes, _ := ioutil.ReadFile(basicExample)\n\tmodelString := string(modelBytes)\n\tm, err := NewModelFromString(modelString)\n\tif err != nil {\n\t\tt.Errorf(\"model failed to load from string: %s\", err)\n\t}\n\tif m == nil {\n\t\tt.Error(\"model should not be nil\")\n\t}\n}\n\nfunc TestLoadModelFromConfig(t *testing.T) {\n\tm := NewModel()\n\terr := m.loadModelFromConfig(basicConfig)\n\tif err != nil {\n\t\tt.Error(\"basic config should not return an error\")\n\t}\n\tm = NewModel()\n\terr = m.loadModelFromConfig(&MockConfig{})\n\tif err == nil {\n\t\tt.Error(\"empty config should return error\")\n\t} else {\n\t\t// check for missing sections in message\n\t\tfor _, rs := range requiredSections {\n\t\t\tif !strings.Contains(err.Error(), sectionNameMap[rs]) {\n\t\t\t\tt.Errorf(\"section name: %s should be in message\", sectionNameMap[rs])\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestHasSection(t *testing.T) {\n\tm := NewModel()\n\t_ = m.loadModelFromConfig(basicConfig)\n\tfor _, sec := range requiredSections {\n\t\tif !m.hasSection(sec) {\n\t\t\tt.Errorf(\"%s section was expected in model\", sec)\n\t\t}\n\t}\n\tm = NewModel()\n\t_ = m.loadModelFromConfig(&MockConfig{})\n\tfor _, sec := range requiredSections {\n\t\tif m.hasSection(sec) {\n\t\t\tt.Errorf(\"%s section was not expected in model\", sec)\n\t\t}\n\t}\n}\n\nfunc TestModel_AddDef(t *testing.T) {\n\tm := NewModel()\n\ts := \"r\"\n\tv := \"sub, obj, act\"\n\tok := m.AddDef(s, s, v)\n\tif !ok {\n\t\tt.Errorf(\"non empty assertion should be added\")\n\t}\n\tok = m.AddDef(s, s, \"\")\n\tif ok {\n\t\tt.Errorf(\"empty assertion value should not be added\")\n\t}\n}\n\nfunc TestModelToTest(t *testing.T) {\n\ttestModelToText(t, \"r.sub == p.sub && r.obj == p.obj && r_func(r.act, p.act) && testr_func(r.act, p.act)\", \"r_sub == p_sub && r_obj == p_obj && r_func(r_act, p_act) && testr_func(r_act, p_act)\")\n\ttestModelToText(t, \"r.sub == p.sub && r.obj == p.obj && p_func(r.act, p.act) && testp_func(r.act, p.act)\", \"r_sub == p_sub && r_obj == p_obj && p_func(r_act, p_act) && testp_func(r_act, p_act)\")\n}\n\nfunc testModelToText(t *testing.T, mData, mExpected string) {\n\tm := NewModel()\n\tdata := map[string]string{\n\t\t\"r\": \"sub, obj, act\",\n\t\t\"p\": \"sub, obj, act\",\n\t\t\"e\": \"some(where (p.eft == allow))\",\n\t\t\"m\": mData,\n\t}\n\texpected := map[string]string{\n\t\t\"r\": \"sub, obj, act\",\n\t\t\"p\": \"sub, obj, act\",\n\t\t\"e\": constant.AllowOverrideEffect,\n\t\t\"m\": mExpected,\n\t}\n\taddData := func(ptype string) {\n\t\tm.AddDef(ptype, ptype, data[ptype])\n\t}\n\tfor ptype := range data {\n\t\taddData(ptype)\n\t}\n\tnewM := NewModel()\n\tprint(m.ToText())\n\t_ = newM.LoadModelFromText(m.ToText())\n\tfor ptype := range data {\n\t\tif newM[ptype][ptype].Value != expected[ptype] {\n\t\t\tt.Errorf(\"\\\"%s\\\" assertion value changed, current value: %s, it should be: %s\", ptype, newM[ptype][ptype].Value, expected[ptype])\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "model/policy.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage model\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/rbac\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\ntype (\n\tPolicyOp int\n)\n\nconst (\n\tPolicyAdd PolicyOp = iota\n\tPolicyRemove\n)\n\nconst DefaultSep = \",\"\n\n// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.\nfunc (model Model) BuildIncrementalRoleLinks(rmMap map[string]rbac.RoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {\n\tif sec == \"g\" && rmMap[ptype] != nil {\n\t\t_, err := model.GetAssertion(sec, ptype)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn model[sec][ptype].buildIncrementalRoleLinks(rmMap[ptype], op, rules)\n\t}\n\treturn nil\n}\n\n// BuildRoleLinks initializes the roles in RBAC.\nfunc (model Model) BuildRoleLinks(rmMap map[string]rbac.RoleManager) error {\n\tmodel.PrintPolicy()\n\tfor ptype, ast := range model[\"g\"] {\n\t\tif rm := rmMap[ptype]; rm != nil {\n\t\t\terr := ast.buildRoleLinks(rm)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.\nfunc (model Model) BuildIncrementalConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {\n\tif sec == \"g\" && condRmMap[ptype] != nil {\n\t\t_, err := model.GetAssertion(sec, ptype)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn model[sec][ptype].buildIncrementalConditionalRoleLinks(condRmMap[ptype], op, rules)\n\t}\n\treturn nil\n}\n\n// BuildConditionalRoleLinks initializes the roles in RBAC.\nfunc (model Model) BuildConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager) error {\n\tmodel.PrintPolicy()\n\tfor ptype, ast := range model[\"g\"] {\n\t\tif condRm := condRmMap[ptype]; condRm != nil {\n\t\t\terr := ast.buildConditionalRoleLinks(condRm)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// PrintPolicy prints the policy to log.\nfunc (model Model) PrintPolicy() {\n\t// Logger has been removed - this is now a no-op\n}\n\n// ClearPolicy clears all current policy.\nfunc (model Model) ClearPolicy() {\n\tfor _, ast := range model[\"p\"] {\n\t\tast.Policy = nil\n\t\tast.PolicyMap = map[string]int{}\n\t}\n\n\tfor _, ast := range model[\"g\"] {\n\t\tast.Policy = nil\n\t\tast.PolicyMap = map[string]int{}\n\t}\n}\n\n// GetPolicy gets all rules in a policy.\nfunc (model Model) GetPolicy(sec string, ptype string) ([][]string, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn model[sec][ptype].Policy, nil\n}\n\n// GetFilteredPolicy gets rules based on field filters from a policy.\nfunc (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres := [][]string{}\n\n\tfor _, rule := range model[sec][ptype].Policy {\n\t\tmatched := true\n\t\tfor i, fieldValue := range fieldValues {\n\t\t\tif fieldValue != \"\" && rule[fieldIndex+i] != fieldValue {\n\t\t\t\tmatched = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif matched {\n\t\t\tres = append(res, rule)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\n// HasPolicyEx determines whether a model has the specified policy rule with error.\nfunc (model Model) HasPolicyEx(sec string, ptype string, rule []string) (bool, error) {\n\tassertion, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tswitch sec {\n\tcase \"p\":\n\t\tif len(rule) != len(assertion.Tokens) {\n\t\t\treturn false, fmt.Errorf(\n\t\t\t\t\"invalid policy rule size: expected %d, got %d, rule: %v\",\n\t\t\t\tlen(model[\"p\"][ptype].Tokens),\n\t\t\t\tlen(rule),\n\t\t\t\trule)\n\t\t}\n\tcase \"g\":\n\t\tif len(rule) < len(assertion.Tokens) {\n\t\t\treturn false, fmt.Errorf(\n\t\t\t\t\"invalid policy rule size: expected %d, got %d, rule: %v\",\n\t\t\t\tlen(model[\"g\"][ptype].Tokens),\n\t\t\t\tlen(rule),\n\t\t\t\trule)\n\t\t}\n\t}\n\treturn model.HasPolicy(sec, ptype, rule)\n}\n\n// HasPolicy determines whether a model has the specified policy rule.\nfunc (model Model) HasPolicy(sec string, ptype string, rule []string) (bool, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t_, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]\n\treturn ok, nil\n}\n\n// HasPolicies determines whether a model has any of the specified policies. If one is found we return true.\nfunc (model Model) HasPolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\tfor i := 0; i < len(rules); i++ {\n\t\tok, err := model.HasPolicy(sec, ptype, rules[i])\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif ok {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\n// AddPolicy adds a policy rule to the model.\nfunc (model Model) AddPolicy(sec string, ptype string, rule []string) error {\n\tassertion, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn err\n\t}\n\tassertion.Policy = append(assertion.Policy, rule)\n\tassertion.PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1\n\n\thasPriority := false\n\tif _, ok := assertion.FieldIndexMap[constant.PriorityIndex]; ok {\n\t\thasPriority = true\n\t}\n\tif sec == \"p\" && hasPriority {\n\t\tif idxInsert, err := strconv.Atoi(rule[assertion.FieldIndexMap[constant.PriorityIndex]]); err == nil {\n\t\t\ti := len(assertion.Policy) - 1\n\t\t\tfor ; i > 0; i-- {\n\t\t\t\tidx, err := strconv.Atoi(assertion.Policy[i-1][assertion.FieldIndexMap[constant.PriorityIndex]])\n\t\t\t\tif err != nil || idx <= idxInsert {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tassertion.Policy[i] = assertion.Policy[i-1]\n\t\t\t\tassertion.PolicyMap[strings.Join(assertion.Policy[i-1], DefaultSep)]++\n\t\t\t}\n\t\t\tassertion.Policy[i] = rule\n\t\t\tassertion.PolicyMap[strings.Join(rule, DefaultSep)] = i\n\t\t}\n\t}\n\treturn nil\n}\n\n// AddPolicies adds policy rules to the model.\nfunc (model Model) AddPolicies(sec string, ptype string, rules [][]string) error {\n\t_, err := model.AddPoliciesWithAffected(sec, ptype, rules)\n\treturn err\n}\n\n// AddPoliciesWithAffected adds policy rules to the model, and returns affected rules.\nfunc (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar affected [][]string\n\tfor _, rule := range rules {\n\t\thashKey := strings.Join(rule, DefaultSep)\n\t\t_, ok := model[sec][ptype].PolicyMap[hashKey]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\t\taffected = append(affected, rule)\n\t\terr = model.AddPolicy(sec, ptype, rule)\n\t\tif err != nil {\n\t\t\treturn affected, err\n\t\t}\n\t}\n\treturn affected, err\n}\n\n// RemovePolicy removes a policy rule from the model.\n// Deprecated: Using AddPoliciesWithAffected instead.\nfunc (model Model) RemovePolicy(sec string, ptype string, rule []string) (bool, error) {\n\tast, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tkey := strings.Join(rule, DefaultSep)\n\tindex, ok := ast.PolicyMap[key]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\n\tlastIdx := len(ast.Policy) - 1\n\tif index != lastIdx {\n\t\tast.Policy[index] = ast.Policy[lastIdx]\n\t\tlastPolicyKey := strings.Join(ast.Policy[index], DefaultSep)\n\t\tast.PolicyMap[lastPolicyKey] = index\n\t}\n\tast.Policy = ast.Policy[:lastIdx]\n\tdelete(ast.PolicyMap, key)\n\treturn true, nil\n}\n\n// UpdatePolicy updates a policy rule from the model.\nfunc (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\toldPolicy := strings.Join(oldRule, DefaultSep)\n\tindex, ok := model[sec][ptype].PolicyMap[oldPolicy]\n\tif !ok {\n\t\treturn false, nil\n\t}\n\n\tmodel[sec][ptype].Policy[index] = newRule\n\tdelete(model[sec][ptype].PolicyMap, oldPolicy)\n\tmodel[sec][ptype].PolicyMap[strings.Join(newRule, DefaultSep)] = index\n\n\treturn true, nil\n}\n\n// UpdatePolicies updates a policy rule from the model.\nfunc (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\trollbackFlag := false\n\t// index -> []{oldIndex, newIndex}\n\tmodifiedRuleIndex := make(map[int][]int)\n\t// rollback\n\tdefer func() {\n\t\tif rollbackFlag {\n\t\t\tfor index, oldNewIndex := range modifiedRuleIndex {\n\t\t\t\tmodel[sec][ptype].Policy[index] = oldRules[oldNewIndex[0]]\n\t\t\t\toldPolicy := strings.Join(oldRules[oldNewIndex[0]], DefaultSep)\n\t\t\t\tnewPolicy := strings.Join(newRules[oldNewIndex[1]], DefaultSep)\n\t\t\t\tdelete(model[sec][ptype].PolicyMap, newPolicy)\n\t\t\t\tmodel[sec][ptype].PolicyMap[oldPolicy] = index\n\t\t\t}\n\t\t}\n\t}()\n\n\tnewIndex := 0\n\tfor oldIndex, oldRule := range oldRules {\n\t\toldPolicy := strings.Join(oldRule, DefaultSep)\n\t\tindex, ok := model[sec][ptype].PolicyMap[oldPolicy]\n\t\tif !ok {\n\t\t\trollbackFlag = true\n\t\t\treturn false, nil\n\t\t}\n\n\t\tmodel[sec][ptype].Policy[index] = newRules[newIndex]\n\t\tdelete(model[sec][ptype].PolicyMap, oldPolicy)\n\t\tmodel[sec][ptype].PolicyMap[strings.Join(newRules[newIndex], DefaultSep)] = index\n\t\tmodifiedRuleIndex[index] = []int{oldIndex, newIndex}\n\t\tnewIndex++\n\t}\n\n\treturn true, nil\n}\n\n// RemovePolicies removes policy rules from the model.\nfunc (model Model) RemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {\n\taffected, err := model.RemovePoliciesWithAffected(sec, ptype, rules)\n\treturn len(affected) != 0, err\n}\n\n// RemovePoliciesWithAffected removes policy rules from the model, and returns affected rules.\nfunc (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar affected [][]string\n\tfor _, rule := range rules {\n\t\tindex, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\taffected = append(affected, rule)\n\t\tmodel[sec][ptype].Policy = append(model[sec][ptype].Policy[:index], model[sec][ptype].Policy[index+1:]...)\n\t\tdelete(model[sec][ptype].PolicyMap, strings.Join(rule, DefaultSep))\n\t\tfor i := index; i < len(model[sec][ptype].Policy); i++ {\n\t\t\tmodel[sec][ptype].PolicyMap[strings.Join(model[sec][ptype].Policy[i], DefaultSep)] = i\n\t\t}\n\t}\n\treturn affected, nil\n}\n\n// RemoveFilteredPolicy removes policy rules based on field filters from the model.\nfunc (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string, error) {\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tvar tmp [][]string\n\tvar effects [][]string\n\tres := false\n\tmodel[sec][ptype].PolicyMap = map[string]int{}\n\n\tfor _, rule := range model[sec][ptype].Policy {\n\t\tmatched := true\n\t\tfor i, fieldValue := range fieldValues {\n\t\t\tif fieldValue != \"\" && rule[fieldIndex+i] != fieldValue {\n\t\t\t\tmatched = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif matched {\n\t\t\teffects = append(effects, rule)\n\t\t} else {\n\t\t\ttmp = append(tmp, rule)\n\t\t\tmodel[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(tmp) - 1\n\t\t}\n\t}\n\n\tif len(tmp) != len(model[sec][ptype].Policy) {\n\t\tmodel[sec][ptype].Policy = tmp\n\t\tres = true\n\t}\n\n\treturn res, effects, nil\n}\n\n// GetValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed.\nfunc (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) ([]string, error) {\n\tvalues := []string{}\n\n\t_, err := model.GetAssertion(sec, ptype)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, rule := range model[sec][ptype].Policy {\n\t\tvalues = append(values, rule[fieldIndex])\n\t}\n\n\tutil.ArrayRemoveDuplicates(&values)\n\n\treturn values, nil\n}\n\n// GetValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.\nfunc (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) ([]string, error) {\n\tvalues := []string{}\n\n\tfor ptype := range model[sec] {\n\t\tv, err := model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvalues = append(values, v...)\n\t}\n\n\tutil.ArrayRemoveDuplicates(&values)\n\n\treturn values, nil\n}\n\n// GetValuesForFieldInPolicyAllTypesByName gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.\nfunc (model Model) GetValuesForFieldInPolicyAllTypesByName(sec string, field string) ([]string, error) {\n\tvalues := []string{}\n\n\tfor ptype := range model[sec] {\n\t\t// GetFieldIndex will return (-1, err) if field is not found, ignore it\n\t\tindex, err := model.GetFieldIndex(ptype, field)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tv, err := model.GetValuesForFieldInPolicy(sec, ptype, index)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvalues = append(values, v...)\n\t}\n\n\tutil.ArrayRemoveDuplicates(&values)\n\n\treturn values, nil\n}\n"
  },
  {
    "path": "model_b_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc rawEnforce(sub string, obj string, act string) bool {\n\tpolicy := [2][3]string{{\"alice\", \"data1\", \"read\"}, {\"bob\", \"data2\", \"write\"}}\n\tfor _, rule := range policy {\n\t\tif sub == rule[0] && obj == rule[1] && act == rule[2] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc BenchmarkRaw(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\trawEnforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkBasicModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data2\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelSizes(b *testing.B) {\n\tcases := []struct {\n\t\tname      string\n\t\troles     int\n\t\tresources int\n\t\tusers     int\n\t}{\n\t\t{name: \"small\", roles: 100, resources: 10, users: 1000},\n\t\t{name: \"medium\", roles: 1000, resources: 100, users: 10000},\n\t\t{name: \"large\", roles: 10000, resources: 1000, users: 100000},\n\t}\n\tfor _, c := range cases {\n\t\tc := c\n\n\t\te, err := NewEnforcer(\"examples/rbac_model.conf\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tpPolicies := make([][]string, c.roles)\n\t\tfor i := range pPolicies {\n\t\t\tpPolicies[i] = []string{\n\t\t\t\tfmt.Sprintf(\"group-has-a-very-long-name-%d\", i),\n\t\t\t\tfmt.Sprintf(\"data-has-a-very-long-name-%d\", i%c.resources),\n\t\t\t\t\"read\",\n\t\t\t}\n\t\t}\n\t\tif _, err := e.AddPolicies(pPolicies); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\tgPolicies := make([][]string, c.users)\n\t\tfor i := range gPolicies {\n\t\t\tgPolicies[i] = []string{\n\t\t\t\tfmt.Sprintf(\"user-has-a-very-long-name-%d\", i),\n\t\t\t\tfmt.Sprintf(\"group-has-a-very-long-name-%d\", i%c.roles),\n\t\t\t}\n\t\t}\n\t\tif _, err := e.AddGroupingPolicies(gPolicies); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\n\t\t// Set up enforcements, alternating between things a user can access\n\t\t// and things they cannot. Use 17 tests so that we get a variety of users\n\t\t// and roles rather than always landing on a multiple of 2/10/whatever.\n\t\tenforcements := make([][]interface{}, 17)\n\t\tfor i := range enforcements {\n\t\t\tuserNum := (c.users / len(enforcements)) * i\n\t\t\troleNum := userNum % c.roles\n\t\t\tresourceNum := roleNum % c.resources\n\t\t\tif i%2 == 0 {\n\t\t\t\tresourceNum += 1\n\t\t\t\tresourceNum %= c.resources\n\t\t\t}\n\t\t\tenforcements[i] = []interface{}{\n\t\t\t\tfmt.Sprintf(\"user-has-a-very-long-name-%d\", userNum),\n\t\t\t\tfmt.Sprintf(\"data-has-a-very-long-name-%d\", resourceNum),\n\t\t\t\t\"read\",\n\t\t\t}\n\t\t}\n\n\t\tb.Run(c.name, func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t_, _ = e.Enforce(enforcements[i%len(enforcements)]...)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkRBACModelSmall(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\n\t// 100 roles, 10 resources.\n\tfor i := 0; i < 100; i++ {\n\t\t_, err := e.AddPolicy(fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\")\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n\n\t// 1000 users.\n\tfor i := 0; i < 1000; i++ {\n\t\t_, err := e.AddGroupingPolicy(fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10))\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user501\", \"data9\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelMedium(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 10000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user5001\", \"data99\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 100000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 100000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"user50001\", \"data999\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelWithResourceRoles(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_with_resource_roles_model.conf\", \"examples/rbac_with_resource_roles_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelWithDomains(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"domain1\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkABACModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/abac_model.conf\")\n\tdata1 := newTestResource(\"data1\", \"alice\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", data1, \"read\")\n\t}\n}\n\nfunc BenchmarkABACRuleModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/abac_rule_model.conf\")\n\tsub := newTestSubject(\"alice\", 18)\n\n\tfor i := 0; i < 1000; i++ {\n\t\t_, _ = e.AddPolicy(\"r.sub.Age > 20\", fmt.Sprintf(\"data%d\", i), \"read\")\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(sub, \"data100\", \"read\")\n\t}\n}\n\nfunc BenchmarkKeyMatchModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/keymatch_model.conf\", \"examples/keymatch_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"/alice_data/resource1\", \"GET\")\n\t}\n}\n\nfunc BenchmarkRBACModelWithDeny(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_with_deny_model.conf\", \"examples/rbac_with_deny_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkPriorityModel(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/priority_model.conf\", \"examples/priority_policy.csv\")\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"alice\", \"data1\", \"read\")\n\t}\n}\n\nfunc BenchmarkRBACModelWithDomainPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\t_ = e.BuildRoleLinks()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = e.Enforce(\"staffUser1001\", \"/orgs/1/sites/site001\", \"App001.Module001.Action1001\")\n\t}\n}\n"
  },
  {
    "path": "model_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\tfileadapter \"github.com/casbin/casbin/v3/persist/file-adapter\"\n\t\"github.com/casbin/casbin/v3/rbac\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc testEnforce(t *testing.T, e *Enforcer, sub interface{}, obj interface{}, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, obj, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, %v, %s: %t, supposed to be %t\", sub, obj, act, myRes, res)\n\t}\n}\n\nfunc testEnforceWithoutUsers(t *testing.T, e *Enforcer, obj string, act string, res bool) {\n\tt.Helper()\n\tif myRes, _ := e.Enforce(obj, act); myRes != res {\n\t\tt.Errorf(\"%s, %s: %t, supposed to be %t\", obj, act, myRes, res)\n\t}\n}\n\nfunc testDomainEnforce(t *testing.T, e *Enforcer, sub string, dom string, obj string, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, dom, obj, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, %s, %s, %s: %t, supposed to be %t\", sub, dom, obj, act, myRes, res)\n\t}\n}\n\nfunc TestBasicModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestBasicModelWithoutSpaces(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model_without_spaces.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestBasicModelNoPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_model.conf\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n}\n\nfunc TestBasicModelWithRoot(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_with_root_model.conf\", \"examples/basic_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"root\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"root\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"root\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"root\", \"data2\", \"write\", true)\n}\n\nfunc TestBasicModelWithRootNoPolicy(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_with_root_model.conf\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"root\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"root\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"root\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"root\", \"data2\", \"write\", true)\n}\n\nfunc TestBasicModelWithoutUsers(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_without_users_model.conf\", \"examples/basic_without_users_policy.csv\")\n\n\ttestEnforceWithoutUsers(t, e, \"data1\", \"read\", true)\n\ttestEnforceWithoutUsers(t, e, \"data1\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"data2\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"data2\", \"write\", true)\n}\n\nfunc TestBasicModelWithoutResources(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_without_resources_model.conf\", \"examples/basic_without_resources_policy.csv\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", true)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n}\n\nfunc TestRBACModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithResourceRoles(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_resource_roles_model.conf\", \"examples/rbac_with_resource_roles_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithDomains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithDomainsAtRuntime(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\")\n\n\t_, _ = e.AddPolicy(\"admin\", \"domain1\", \"data1\", \"read\")\n\t_, _ = e.AddPolicy(\"admin\", \"domain1\", \"data1\", \"write\")\n\t_, _ = e.AddPolicy(\"admin\", \"domain2\", \"data2\", \"read\")\n\t_, _ = e.AddPolicy(\"admin\", \"domain2\", \"data2\", \"write\")\n\n\t_, _ = e.AddGroupingPolicy(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.AddGroupingPolicy(\"bob\", \"admin\", \"domain2\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"write\", true)\n\n\t// Remove all policy rules related to domain1 and data1.\n\t_, _ = e.RemoveFilteredPolicy(1, \"domain1\", \"data1\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"write\", true)\n\n\t// Remove the specified policy rule.\n\t_, _ = e.RemovePolicy(\"admin\", \"domain2\", \"data2\", \"read\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithDomainsAtRuntimeMockAdapter(t *testing.T) {\n\tadapter := fileadapter.NewAdapterMock(\"examples/rbac_with_domains_policy.csv\")\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", adapter)\n\n\t_, _ = e.AddPolicy(\"admin\", \"domain3\", \"data1\", \"read\")\n\t_, _ = e.AddGroupingPolicy(\"alice\", \"admin\", \"domain3\")\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain3\", \"data1\", \"read\", true)\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\t_, _ = e.RemoveFilteredPolicy(1, \"domain1\", \"data1\")\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", false)\n\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", true)\n\t_, _ = e.RemovePolicy(\"admin\", \"domain2\", \"data2\", \"read\")\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", false)\n}\n\nfunc TestRBACModelWithDomainTokenRename(t *testing.T) {\n\t// Test that renaming the domain token from \"dom\" to another name (e.g., \"dom1\")\n\t// still works correctly. This is a regression test for the issue where the\n\t// hardcoded \"r_dom\" and \"p_dom\" strings prevented proper domain matching.\n\n\t// Test with standard \"dom\" token\n\tmodelText1 := `\n[request_definition]\nr = sub, dom, obj, act\n\n[policy_definition]\np = sub, dom, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.act\n`\n\tm1, _ := model.NewModelFromString(modelText1)\n\te1, _ := NewEnforcer(m1)\n\t_, _ = e1.AddPolicy(\"admin\", \"domain1\", \"data1\", \"read\")\n\t_, _ = e1.AddGroupingPolicy(\"alice\", \"admin\", \"domain*\")\n\n\ttestDomainEnforce(t, e1, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e1, \"alice\", \"domain2\", \"data1\", \"read\", false)\n\n\t// Test with renamed \"dom1\" token\n\tmodelText2 := `\n[request_definition]\nr = sub, dom1, obj, act\n\n[policy_definition]\np = sub, dom1, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.dom1) && keyMatch(r.dom1, p.dom1) && r.obj == p.obj && r.act == p.act\n`\n\tm2, _ := model.NewModelFromString(modelText2)\n\te2, _ := NewEnforcer(m2)\n\t_, _ = e2.AddPolicy(\"admin\", \"domain1\", \"data1\", \"read\")\n\t_, _ = e2.AddGroupingPolicy(\"alice\", \"admin\", \"domain*\")\n\n\ttestDomainEnforce(t, e2, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e2, \"alice\", \"domain2\", \"data1\", \"read\", false)\n\n\t// Test with renamed \"tenant\" token\n\tmodelText3 := `\n[request_definition]\nr = sub, tenant, obj, act\n\n[policy_definition]\np = sub, tenant, obj, act\n\n[role_definition]\ng = _, _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub, r.tenant) && keyMatch(r.tenant, p.tenant) && r.obj == p.obj && r.act == p.act\n`\n\tm3, _ := model.NewModelFromString(modelText3)\n\te3, _ := NewEnforcer(m3)\n\t_, _ = e3.AddPolicy(\"admin\", \"domain1\", \"data1\", \"read\")\n\t_, _ = e3.AddGroupingPolicy(\"alice\", \"admin\", \"domain*\")\n\n\ttestDomainEnforce(t, e3, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e3, \"alice\", \"domain2\", \"data1\", \"read\", false)\n}\n\nfunc TestRBACModelWithDeny(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_deny_model.conf\", \"examples/rbac_with_deny_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithOnlyDeny(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_not_deny_model.conf\", \"examples/rbac_with_deny_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n}\n\nfunc TestRBACModelWithCustomData(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t// You can add custom data to a grouping policy, Casbin will ignore it. It is only meaningful to the caller.\n\t// This feature can be used to store information like whether \"bob\" is an end user (so no subject will inherit \"bob\")\n\t// For Casbin, it is equivalent to: e.AddGroupingPolicy(\"bob\", \"data2_admin\")\n\t_, _ = e.AddGroupingPolicy(\"bob\", \"data2_admin\", \"custom_data\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t// You should also take the custom data as a parameter when deleting a grouping policy.\n\t// e.RemoveGroupingPolicy(\"bob\", \"data2_admin\") won't work.\n\t// Or you can remove it by using RemoveFilteredGroupingPolicy().\n\t_, _ = e.RemoveGroupingPolicy(\"bob\", \"data2_admin\", \"custom_data\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRBACModelWithPattern(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\n\t// Here's a little confusing: the matching function here is not the custom function used in matcher.\n\t// It is the matching function used by \"g\" (and \"g2\", \"g3\" if any..)\n\t// You can see in policy that: \"g2, /book/:id, book_group\", so in \"g2()\" function in the matcher, instead\n\t// of checking whether \"/book/:id\" equals the obj: \"/book/1\", it checks whether the pattern matches.\n\t// You can see it as normal RBAC: \"/book/:id\" == \"/book/1\" becomes KeyMatch2(\"/book/:id\", \"/book/1\")\n\te.AddNamedMatchingFunc(\"g2\", \"KeyMatch2\", util.KeyMatch2)\n\te.AddNamedMatchingFunc(\"g\", \"KeyMatch2\", util.KeyMatch2)\n\ttestEnforce(t, e, \"any_user\", \"/pen3/1\", \"GET\", true)\n\ttestEnforce(t, e, \"/book/user/1\", \"/pen4/1\", \"GET\", true)\n\n\ttestEnforce(t, e, \"/book/user/1\", \"/pen4/1\", \"POST\", true)\n\n\ttestEnforce(t, e, \"alice\", \"/book/1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/book/2\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/pen/1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/pen/2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/book/1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/book/2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/pen/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen/2\", \"GET\", true)\n\n\t// AddMatchingFunc() is actually setting a function because only one function is allowed,\n\t// so when we set \"KeyMatch3\", we are actually replacing \"KeyMatch2\" with \"KeyMatch3\".\n\te.AddNamedMatchingFunc(\"g2\", \"KeyMatch2\", util.KeyMatch3)\n\ttestEnforce(t, e, \"alice\", \"/book2/1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/book2/2\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/pen2/1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/pen2/2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/book2/1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/book2/2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/pen2/1\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/pen2/2\", \"GET\", true)\n}\n\nfunc TestRBACModelWithDifferentTypesOfRoles(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_different_types_of_roles_model.conf\", \"examples/rbac_with_different_types_of_roles_policy.csv\")\n\n\tg, err := e.GetNamedGroupingPolicy(\"g\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfor _, gp := range g {\n\t\tif len(gp) != 5 {\n\t\t\tt.Error(\"g parameters' num isn't 5\")\n\t\t\treturn\n\t\t}\n\t\te.AddNamedDomainLinkConditionFunc(\"g\", gp[0], gp[1], gp[2], util.TimeMatchFunc)\n\t}\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"carol\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"carol\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"carol\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"carol\", \"data2\", \"write\", false)\n}\n\ntype testCustomRoleManager struct{}\n\nfunc NewRoleManager() rbac.RoleManager {\n\treturn &testCustomRoleManager{}\n}\nfunc (rm *testCustomRoleManager) Clear() error { return nil }\nfunc (rm *testCustomRoleManager) AddLink(name1 string, name2 string, domain ...string) error {\n\treturn nil\n}\nfunc (rm *testCustomRoleManager) BuildRelationship(name1 string, name2 string, domain ...string) error {\n\treturn nil\n}\nfunc (rm *testCustomRoleManager) DeleteLink(name1 string, name2 string, domain ...string) error {\n\treturn nil\n}\nfunc (rm *testCustomRoleManager) HasLink(name1 string, name2 string, domain ...string) (bool, error) {\n\tif name1 == \"alice\" && name2 == \"alice\" {\n\t\treturn true, nil\n\t} else if name1 == \"alice\" && name2 == \"data2_admin\" {\n\t\treturn true, nil\n\t} else if name1 == \"bob\" && name2 == \"bob\" {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\nfunc (rm *testCustomRoleManager) GetRoles(name string, domain ...string) ([]string, error) {\n\treturn []string{}, nil\n}\nfunc (rm *testCustomRoleManager) GetUsers(name string, domain ...string) ([]string, error) {\n\treturn []string{}, nil\n}\nfunc (rm *testCustomRoleManager) GetDomains(name string) ([]string, error) {\n\treturn []string{}, nil\n}\nfunc (rm *testCustomRoleManager) GetAllDomains() ([]string, error) {\n\treturn []string{}, nil\n}\nfunc (rm *testCustomRoleManager) PrintRoles() error { return nil }\n\nfunc (rm *testCustomRoleManager) Match(str string, pattern string) bool                   { return true }\nfunc (rm *testCustomRoleManager) AddMatchingFunc(name string, fn rbac.MatchingFunc)       {}\nfunc (rm *testCustomRoleManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {}\n\nfunc (rm *testCustomRoleManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {\n}\nfunc (rm *testCustomRoleManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {\n}\nfunc (rm *testCustomRoleManager) AddDomainLinkConditionFunc(user string, role string, domain string, fn rbac.LinkConditionFunc) {\n}\nfunc (rm *testCustomRoleManager) SetDomainLinkConditionFuncParams(user string, role string, domain string, params ...string) {\n}\n\nfunc (rm *testCustomRoleManager) DeleteDomain(domain string) error {\n\treturn nil\n}\n\nfunc (rm *testCustomRoleManager) GetImplicitRoles(name string, domain ...string) ([]string, error) {\n\treturn []string{}, nil\n}\n\nfunc (rm *testCustomRoleManager) GetImplicitUsers(name string, domain ...string) ([]string, error) {\n\treturn []string{}, nil\n}\n\nfunc TestRBACModelWithCustomRoleManager(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\te.SetRoleManager(NewRoleManager())\n\t_ = e.LoadModel()\n\t_ = e.LoadPolicy()\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestKeyMatchModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/keymatch_model.conf\", \"examples/keymatch_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/bob_data/resource2\", \"POST\", false)\n\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource1\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"GET\", true)\n\ttestEnforce(t, e, \"bob\", \"/alice_data/resource2\", \"POST\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource1\", \"POST\", true)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"GET\", false)\n\ttestEnforce(t, e, \"bob\", \"/bob_data/resource2\", \"POST\", true)\n\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"GET\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"POST\", true)\n\ttestEnforce(t, e, \"cathy\", \"/cathy_data\", \"DELETE\", false)\n}\n\nfunc TestKeyMatch2Model(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/keymatch2_model.conf\", \"examples/keymatch2_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/alice_data/resource1\", \"GET\", true)\n\ttestEnforce(t, e, \"alice\", \"/alice_data2/myid\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/alice_data2/myid/using/res_id\", \"GET\", true)\n}\n\nfunc CustomFunction(key1 string, key2 string) bool {\n\tif key1 == \"/alice_data2/myid/using/res_id\" && key2 == \"/alice_data/:resource\" {\n\t\treturn true\n\t} else if key1 == \"/alice_data2/myid/using/res_id\" && key2 == \"/alice_data2/:id/using/:resId\" {\n\t\treturn true\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc CustomFunctionWrapper(args ...interface{}) (interface{}, error) {\n\tkey1 := args[0].(string)\n\tkey2 := args[1].(string)\n\n\treturn CustomFunction(key1, key2), nil\n}\n\nfunc TestKeyMatchCustomModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/keymatch_custom_model.conf\", \"examples/keymatch2_policy.csv\")\n\n\te.AddFunction(\"keyMatchCustom\", CustomFunctionWrapper)\n\n\ttestEnforce(t, e, \"alice\", \"/alice_data2/myid\", \"GET\", false)\n\ttestEnforce(t, e, \"alice\", \"/alice_data2/myid/using/res_id\", \"GET\", true)\n}\n\nfunc TestIPMatchModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/ipmatch_model.conf\", \"examples/ipmatch_policy.csv\")\n\n\ttestEnforce(t, e, \"192.168.2.123\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"192.168.2.123\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"192.168.2.123\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"192.168.2.123\", \"data2\", \"write\", false)\n\n\ttestEnforce(t, e, \"192.168.0.123\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"192.168.0.123\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"192.168.0.123\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"192.168.0.123\", \"data2\", \"write\", false)\n\n\ttestEnforce(t, e, \"10.0.0.5\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"10.0.0.5\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"10.0.0.5\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"10.0.0.5\", \"data2\", \"write\", true)\n\n\ttestEnforce(t, e, \"192.168.0.1\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"192.168.0.1\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"192.168.0.1\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"192.168.0.1\", \"data2\", \"write\", false)\n}\n\nfunc TestGlobMatchModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/glob_model.conf\", \"examples/glob_policy.csv\")\n\ttestEnforce(t, e, \"u1\", \"/foo/\", \"read\", true)\n\ttestEnforce(t, e, \"u1\", \"/foo\", \"read\", false)\n\ttestEnforce(t, e, \"u1\", \"/foo/subprefix\", \"read\", true)\n\ttestEnforce(t, e, \"u1\", \"foo\", \"read\", false)\n\n\ttestEnforce(t, e, \"u2\", \"/foosubprefix\", \"read\", true)\n\ttestEnforce(t, e, \"u2\", \"/foo/subprefix\", \"read\", false)\n\ttestEnforce(t, e, \"u2\", \"foo\", \"read\", false)\n\n\ttestEnforce(t, e, \"u3\", \"/prefix/foo/subprefix\", \"read\", true)\n\ttestEnforce(t, e, \"u3\", \"/prefix/foo/\", \"read\", true)\n\ttestEnforce(t, e, \"u3\", \"/prefix/foo\", \"read\", false)\n\n\ttestEnforce(t, e, \"u4\", \"/foo\", \"read\", false)\n\ttestEnforce(t, e, \"u4\", \"foo\", \"read\", true)\n}\n\nfunc TestPriorityModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/priority_model.conf\", \"examples/priority_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n}\n\nfunc TestPriorityModelIndeterminate(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/priority_model.conf\", \"examples/priority_indeterminate_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n}\n\nfunc TestRBACModelInMultiLines(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model_in_multi_line.conf\", \"examples/rbac_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestCommentModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/comment_model.conf\", \"examples/basic_policy.csv\")\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestDomainMatchModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domain_pattern_model.conf\", \"examples/rbac_with_domain_pattern_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"keyMatch2\", util.KeyMatch2)\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"write\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"read\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"data2\", \"write\", true)\n}\n\nfunc TestAllMatchModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_all_pattern_model.conf\", \"examples/rbac_with_all_pattern_policy.csv\")\n\te.AddNamedMatchingFunc(\"g\", \"keyMatch2\", util.KeyMatch2)\n\te.AddNamedDomainMatchingFunc(\"g\", \"keyMatch2\", util.KeyMatch2)\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"/book/1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"/book/1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"/book/1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"/book/1\", \"write\", true)\n}\n\nfunc TestTemporalRolesModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_temporal_roles_model.conf\", \"examples/rbac_with_temporal_roles_policy.csv\")\n\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data2_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data3_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data4_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data5_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data6_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data7_admin\", util.TimeMatchFunc)\n\te.AddNamedLinkConditionFunc(\"g\", \"alice\", \"data8_admin\", util.TimeMatchFunc)\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data3\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data4\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data4\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data5\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data5\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data6\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data6\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data7\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data7\", \"write\", true)\n\ttestEnforce(t, e, \"alice\", \"data8\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data8\", \"write\", false)\n}\n\nfunc TestTemporalRolesModelWithDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domain_temporal_roles_model.conf\", \"examples/rbac_with_domain_temporal_roles_policy.csv\")\n\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data2_admin\", \"domain2\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data3_admin\", \"domain3\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data4_admin\", \"domain4\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data5_admin\", \"domain5\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data6_admin\", \"domain6\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data7_admin\", \"domain7\", util.TimeMatchFunc)\n\te.AddNamedDomainLinkConditionFunc(\"g\", \"alice\", \"data8_admin\", \"domain8\", util.TimeMatchFunc)\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"data1\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain2\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain3\", \"data3\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain3\", \"data3\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain4\", \"data4\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain4\", \"data4\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain5\", \"data5\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain5\", \"data5\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain6\", \"data6\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain6\", \"data6\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain7\", \"data7\", \"read\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain7\", \"data7\", \"write\", true)\n\ttestDomainEnforce(t, e, \"alice\", \"domain8\", \"data8\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain8\", \"data8\", \"write\", false)\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data1\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data1\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data2\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data2\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data3\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data3\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data4\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data4\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data5\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data5\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data6\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data6\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data7\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data7\", \"write\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data8\", \"read\", false)\n\ttestDomainEnforce(t, e, \"alice\", \"domain_not_exist\", \"data8\", \"write\", false)\n}\n\nfunc TestReBACModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rebac_model.conf\", \"examples/rebac_policy.csv\")\n\n\ttestEnforce(t, e, \"alice\", \"doc1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"doc1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"doc2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"doc2\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"doc3\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"doc3\", \"write\", false)\n\n\ttestEnforce(t, e, \"bob\", \"doc1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"doc1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"doc2\", \"read\", true)\n\ttestEnforce(t, e, \"bob\", \"doc2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"doc3\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"doc3\", \"write\", false)\n}\n"
  },
  {
    "path": "orbac_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\n// TestOrBACModel tests the Organization-Based Access Control (OrBAC) model.\n// OrBAC extends RBAC with abstraction layers:\n// - Empower (g): Maps subjects to roles within organizations\n// - Use (g2): Maps concrete actions to abstract activities within organizations\n// - Consider (g3): Maps concrete objects to abstract views within organizations\n// - Permission (p): Grants role-activity-view permissions within organizations\n//\n// This separates concrete entities (subjects, actions, objects) from\n// abstract security entities (roles, activities, views), allowing more\n// flexible and maintainable access control policies.\n\nfunc testEnforceOrBAC(t *testing.T, e *Enforcer, sub string, org string, obj string, act string, res bool) {\n\tt.Helper()\n\tif myRes, err := e.Enforce(sub, org, obj, act); err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t} else if myRes != res {\n\t\tt.Errorf(\"%s, %s, %s, %s: %t, supposed to be %t\", sub, org, obj, act, myRes, res)\n\t}\n}\n\nfunc TestOrBACModel(t *testing.T) {\n\te, err := NewEnforcer(\"examples/orbac_model.conf\", \"examples/orbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Test alice as manager in org1 - can consult (read) and modify (write) documents\n\ttestEnforceOrBAC(t, e, \"alice\", \"org1\", \"data1\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"alice\", \"org1\", \"data1\", \"write\", true)\n\ttestEnforceOrBAC(t, e, \"alice\", \"org1\", \"data2\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"alice\", \"org1\", \"data2\", \"write\", true)\n\n\t// Test bob as employee in org1 - can only consult (read) documents\n\ttestEnforceOrBAC(t, e, \"bob\", \"org1\", \"data1\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"bob\", \"org1\", \"data1\", \"write\", false)\n\ttestEnforceOrBAC(t, e, \"bob\", \"org1\", \"data2\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"bob\", \"org1\", \"data2\", \"write\", false)\n\n\t// Test charlie as manager in org2 - can consult and modify reports\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org2\", \"report1\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org2\", \"report1\", \"write\", true)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org2\", \"report2\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org2\", \"report2\", \"write\", true)\n\n\t// Test david as employee in org2 - can only consult reports\n\ttestEnforceOrBAC(t, e, \"david\", \"org2\", \"report1\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"david\", \"org2\", \"report1\", \"write\", false)\n\ttestEnforceOrBAC(t, e, \"david\", \"org2\", \"report2\", \"read\", true)\n\ttestEnforceOrBAC(t, e, \"david\", \"org2\", \"report2\", \"write\", false)\n\n\t// Test cross-organization access (should be denied)\n\ttestEnforceOrBAC(t, e, \"alice\", \"org2\", \"report1\", \"read\", false)\n\ttestEnforceOrBAC(t, e, \"alice\", \"org2\", \"report1\", \"write\", false)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org1\", \"data1\", \"read\", false)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org1\", \"data1\", \"write\", false)\n\n\t// Test access to objects not in the organization's view\n\ttestEnforceOrBAC(t, e, \"alice\", \"org1\", \"report1\", \"read\", false)\n\ttestEnforceOrBAC(t, e, \"charlie\", \"org2\", \"data1\", \"read\", false)\n}\n"
  },
  {
    "path": "pbac_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\n// Helper function for PBAC enforcement testing.\nfunc testEnforcePBAC(t *testing.T, e *Enforcer, sub interface{}, obj interface{}, act string, res bool) {\n\tt.Helper()\n\tmyRes, err := e.Enforce(sub, obj, act)\n\tif err != nil {\n\t\tt.Errorf(\"Enforce Error: %s\", err)\n\t\treturn\n\t}\n\tif myRes != res {\n\t\tt.Errorf(\"%v, %v, %s: %t, supposed to be %t\", sub, obj, act, myRes, res)\n\t}\n}\n\nfunc TestPBACModel(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/pbac_model.conf\", \"examples/pbac_policy.csv\")\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 25}, map[string]interface{}{\"Type\": \"doc\"}, \"read\", true)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 25}, map[string]interface{}{\"Type\": \"doc\"}, \"read\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"manager\", \"Age\": 30}, map[string]interface{}{\"Type\": \"doc\"}, \"read\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 25}, map[string]interface{}{\"Type\": \"doc\"}, \"write\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 25}, map[string]interface{}{\"Type\": \"doc\"}, \"delete\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 25}, map[string]interface{}{\"Type\": \"video\"}, \"read\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 25}, map[string]interface{}{\"Type\": \"image\"}, \"read\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 18}, map[string]interface{}{\"Type\": \"video\"}, \"play\", true)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 25}, map[string]interface{}{\"Type\": \"video\"}, \"play\", true)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 30}, map[string]interface{}{\"Type\": \"video\"}, \"play\", true)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 16}, map[string]interface{}{\"Type\": \"video\"}, \"play\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 17}, map[string]interface{}{\"Type\": \"video\"}, \"play\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 20}, map[string]interface{}{\"Type\": \"video\"}, \"read\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 20}, map[string]interface{}{\"Type\": \"video\"}, \"write\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 20}, map[string]interface{}{\"Type\": \"doc\"}, \"play\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"user\", \"Age\": 20}, map[string]interface{}{\"Type\": \"image\"}, \"play\", false)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 20}, map[string]interface{}{\"Type\": \"doc\"}, \"read\", true)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"admin\", \"Age\": 20}, map[string]interface{}{\"Type\": \"video\"}, \"play\", true)\n\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"guest\", \"Age\": 15}, map[string]interface{}{\"Type\": \"secret\"}, \"access\", false)\n\ttestEnforcePBAC(t, e, map[string]interface{}{\"Role\": \"visitor\", \"Age\": 25}, map[string]interface{}{\"Type\": \"private\"}, \"view\", false)\n}\n"
  },
  {
    "path": "persist/adapter.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport (\n\t\"encoding/csv\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// LoadPolicyLine loads a text line as a policy rule to model.\nfunc LoadPolicyLine(line string, m model.Model) error {\n\tif line == \"\" || strings.HasPrefix(line, \"#\") {\n\t\treturn nil\n\t}\n\n\tr := csv.NewReader(strings.NewReader(line))\n\tr.Comma = ','\n\tr.Comment = '#'\n\tr.TrimLeadingSpace = true\n\n\ttokens, err := r.Read()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn LoadPolicyArray(tokens, m)\n}\n\n// LoadPolicyArray loads a policy rule to model.\nfunc LoadPolicyArray(rule []string, m model.Model) error {\n\tkey := rule[0]\n\tsec := key[:1]\n\tok, err := m.HasPolicyEx(sec, key, rule[1:])\n\tif err != nil {\n\t\treturn err\n\t}\n\tif ok {\n\t\treturn nil // skip duplicated policy\n\t}\n\n\terr = m.AddPolicy(sec, key, rule[1:])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Adapter is the interface for Casbin adapters.\ntype Adapter interface {\n\t// LoadPolicy loads all policy rules from the storage.\n\tLoadPolicy(model model.Model) error\n\t// SavePolicy saves all policy rules to the storage.\n\tSavePolicy(model model.Model) error\n\n\t// AddPolicy adds a policy rule to the storage.\n\t// This is part of the Auto-Save feature.\n\tAddPolicy(sec string, ptype string, rule []string) error\n\t// RemovePolicy removes a policy rule from the storage.\n\t// This is part of the Auto-Save feature.\n\tRemovePolicy(sec string, ptype string, rule []string) error\n\t// RemoveFilteredPolicy removes policy rules that match the filter from the storage.\n\t// This is part of the Auto-Save feature.\n\tRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error\n}\n"
  },
  {
    "path": "persist/adapter_context.go",
    "content": "// Copyright 2023 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// ContextAdapter provides a context-aware interface for Casbin adapters.\ntype ContextAdapter interface {\n\t// LoadPolicyCtx loads all policy rules from the storage with context.\n\tLoadPolicyCtx(ctx context.Context, model model.Model) error\n\t// SavePolicyCtx saves all policy rules to the storage with context.\n\tSavePolicyCtx(ctx context.Context, model model.Model) error\n\n\t// AddPolicyCtx adds a policy rule to the storage with context.\n\t// This is part of the Auto-Save feature.\n\tAddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error\n\t// RemovePolicyCtx removes a policy rule from the storage with context.\n\t// This is part of the Auto-Save feature.\n\tRemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error\n\t// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.\n\t// This is part of the Auto-Save feature.\n\tRemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error\n}\n"
  },
  {
    "path": "persist/adapter_filtered.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport (\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// FilteredAdapter is the interface for Casbin adapters supporting filtered policies.\ntype FilteredAdapter interface {\n\tAdapter\n\n\t// LoadFilteredPolicy loads only policy rules that match the filter.\n\tLoadFilteredPolicy(model model.Model, filter interface{}) error\n\t// IsFiltered returns true if the loaded policy has been filtered.\n\tIsFiltered() bool\n}\n"
  },
  {
    "path": "persist/adapter_filtered_context.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// ContextFilteredAdapter is the context-aware interface for Casbin adapters supporting filtered policies.\ntype ContextFilteredAdapter interface {\n\tContextAdapter\n\n\t// LoadFilteredPolicyCtx loads only policy rules that match the filter.\n\tLoadFilteredPolicyCtx(ctx context.Context, model model.Model, filter interface{}) error\n\t// IsFilteredCtx returns true if the loaded policy has been filtered.\n\tIsFilteredCtx(ctx context.Context) bool\n}\n"
  },
  {
    "path": "persist/batch_adapter.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\n// BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.\ntype BatchAdapter interface {\n\tAdapter\n\t// AddPolicies adds policy rules to the storage.\n\t// This is part of the Auto-Save feature.\n\tAddPolicies(sec string, ptype string, rules [][]string) error\n\t// RemovePolicies removes policy rules from the storage.\n\t// This is part of the Auto-Save feature.\n\tRemovePolicies(sec string, ptype string, rules [][]string) error\n}\n"
  },
  {
    "path": "persist/batch_adapter_context.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport \"context\"\n\n// ContextBatchAdapter is the context-aware interface for Casbin adapters with multiple add and remove policy functions.\ntype ContextBatchAdapter interface {\n\tContextAdapter\n\n\t// AddPoliciesCtx adds policy rules to the storage.\n\t// This is part of the Auto-Save feature.\n\tAddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error\n\t// RemovePoliciesCtx removes policy rules from the storage.\n\t// This is part of the Auto-Save feature.\n\tRemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error\n}\n"
  },
  {
    "path": "persist/cache/cache.go",
    "content": "// Copyright 2021 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage cache\n\nimport \"errors\"\n\nvar ErrNoSuchKey = errors.New(\"there's no such key existing in cache\")\n\ntype Cache interface {\n\t// Set puts key and value into cache.\n\t// First parameter for extra should be time.Time object denoting expected survival time.\n\t// If survival time equals 0 or less, the key will always be survival.\n\tSet(key string, value bool, extra ...interface{}) error\n\n\t// Get returns result for key,\n\t// If there's no such key existing in cache,\n\t// ErrNoSuchKey will be returned.\n\tGet(key string) (bool, error)\n\n\t// Delete will remove the specific key in cache.\n\t// If there's no such key existing in cache,\n\t// ErrNoSuchKey will be returned.\n\tDelete(key string) error\n\n\t// Clear deletes all the items stored in cache.\n\tClear() error\n}\n"
  },
  {
    "path": "persist/cache/cache_sync.go",
    "content": "// Copyright 2021 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage cache\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\ntype SyncCache struct {\n\tcache DefaultCache\n\tsync.RWMutex\n}\n\nfunc (c *SyncCache) Set(key string, value bool, extra ...interface{}) error {\n\tttl := time.Duration(-1)\n\tif len(extra) > 0 {\n\t\tttl = extra[0].(time.Duration)\n\t}\n\tc.Lock()\n\tdefer c.Unlock()\n\tc.cache[key] = cacheItem{\n\t\tvalue:     value,\n\t\texpiresAt: time.Now().Add(ttl),\n\t\tttl:       ttl,\n\t}\n\treturn nil\n}\n\nfunc (c *SyncCache) Get(key string) (bool, error) {\n\tc.RLock()\n\tres, ok := c.cache[key]\n\tc.RUnlock()\n\tif !ok {\n\t\treturn false, ErrNoSuchKey\n\t} else {\n\t\tif res.ttl > 0 && time.Now().After(res.expiresAt) {\n\t\t\tc.Lock()\n\t\t\tdefer c.Unlock()\n\t\t\tdelete(c.cache, key)\n\t\t\treturn false, ErrNoSuchKey\n\t\t}\n\t\treturn res.value, nil\n\t}\n}\n\nfunc (c *SyncCache) Delete(key string) error {\n\tc.RLock()\n\t_, ok := c.cache[key]\n\tc.RUnlock()\n\tif !ok {\n\t\treturn ErrNoSuchKey\n\t} else {\n\t\tc.Lock()\n\t\tdefer c.Unlock()\n\t\tdelete(c.cache, key)\n\t\treturn nil\n\t}\n}\n\nfunc (c *SyncCache) Clear() error {\n\tc.Lock()\n\tc.cache = make(DefaultCache)\n\tc.Unlock()\n\treturn nil\n}\n\nfunc NewSyncCache() (Cache, error) {\n\tcache := SyncCache{\n\t\tmake(DefaultCache),\n\t\tsync.RWMutex{},\n\t}\n\treturn &cache, nil\n}\n"
  },
  {
    "path": "persist/cache/default-cache.go",
    "content": "// Copyright 2021 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage cache\n\nimport \"time\"\n\ntype cacheItem struct {\n\tvalue     bool\n\texpiresAt time.Time\n\tttl       time.Duration\n}\n\ntype DefaultCache map[string]cacheItem\n\nfunc (c *DefaultCache) Set(key string, value bool, extra ...interface{}) error {\n\tttl := time.Duration(-1)\n\tif len(extra) > 0 {\n\t\tttl = extra[0].(time.Duration)\n\t}\n\t(*c)[key] = cacheItem{\n\t\tvalue:     value,\n\t\texpiresAt: time.Now().Add(ttl),\n\t\tttl:       ttl,\n\t}\n\treturn nil\n}\n\nfunc (c *DefaultCache) Get(key string) (bool, error) {\n\tif res, ok := (*c)[key]; !ok {\n\t\treturn false, ErrNoSuchKey\n\t} else {\n\t\tif res.ttl > 0 && time.Now().After(res.expiresAt) {\n\t\t\tdelete(*c, key)\n\t\t\treturn false, ErrNoSuchKey\n\t\t}\n\t\treturn res.value, nil\n\t}\n}\n\nfunc (c *DefaultCache) Delete(key string) error {\n\tif _, ok := (*c)[key]; !ok {\n\t\treturn ErrNoSuchKey\n\t} else {\n\t\tdelete(*c, key)\n\t\treturn nil\n\t}\n}\n\nfunc (c *DefaultCache) Clear() error {\n\t*c = make(DefaultCache)\n\treturn nil\n}\n\nfunc NewDefaultCache() (Cache, error) {\n\tcache := make(DefaultCache)\n\treturn &cache, nil\n}\n"
  },
  {
    "path": "persist/dispatcher.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\n// Dispatcher is the interface for Casbin dispatcher.\ntype Dispatcher interface {\n\t// AddPolicies adds policies rule to all instance.\n\tAddPolicies(sec string, ptype string, rules [][]string) error\n\t// RemovePolicies removes policies rule from all instance.\n\tRemovePolicies(sec string, ptype string, rules [][]string) error\n\t// RemoveFilteredPolicy removes policy rules that match the filter from all instance.\n\tRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error\n\t// ClearPolicy clears all current policy in all instances\n\tClearPolicy() error\n\t// UpdatePolicy updates policy rule from all instance.\n\tUpdatePolicy(sec string, ptype string, oldRule, newRule []string) error\n\t// UpdatePolicies updates some policy rules from all instance\n\tUpdatePolicies(sec string, ptype string, oldrules, newRules [][]string) error\n\t// UpdateFilteredPolicies deletes old rules and adds new rules.\n\tUpdateFilteredPolicies(sec string, ptype string, oldRules [][]string, newRules [][]string) error\n}\n"
  },
  {
    "path": "persist/file-adapter/adapter.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fileadapter\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// Adapter is the file adapter for Casbin.\n// It can load policy from file or save policy to file.\ntype Adapter struct {\n\tfilePath string\n}\n\nfunc (a *Adapter) UpdatePolicy(sec string, ptype string, oldRule, newRule []string) error {\n\treturn errors.New(\"not implemented\")\n}\n\nfunc (a *Adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {\n\treturn errors.New(\"not implemented\")\n}\n\nfunc (a *Adapter) UpdateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\n// NewAdapter is the constructor for Adapter.\nfunc NewAdapter(filePath string) *Adapter {\n\treturn &Adapter{filePath: filePath}\n}\n\n// LoadPolicy loads all policy rules from the storage.\nfunc (a *Adapter) LoadPolicy(model model.Model) error {\n\tif a.filePath == \"\" {\n\t\treturn errors.New(\"invalid file path, file path cannot be empty\")\n\t}\n\n\treturn a.loadPolicyFile(model, persist.LoadPolicyLine)\n}\n\n// SavePolicy saves all policy rules to the storage.\nfunc (a *Adapter) SavePolicy(model model.Model) error {\n\tif a.filePath == \"\" {\n\t\treturn errors.New(\"invalid file path, file path cannot be empty\")\n\t}\n\n\tvar tmp bytes.Buffer\n\n\tfor ptype, ast := range model[\"p\"] {\n\t\tfor _, rule := range ast.Policy {\n\t\t\ttmp.WriteString(ptype + \", \")\n\t\t\ttmp.WriteString(util.ArrayToString(rule))\n\t\t\ttmp.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\tfor ptype, ast := range model[\"g\"] {\n\t\tfor _, rule := range ast.Policy {\n\t\t\ttmp.WriteString(ptype + \", \")\n\t\t\ttmp.WriteString(util.ArrayToString(rule))\n\t\t\ttmp.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\treturn a.savePolicyFile(strings.TrimRight(tmp.String(), \"\\n\"))\n}\n\nfunc (a *Adapter) loadPolicyFile(model model.Model, handler func(string, model.Model) error) error {\n\tf, err := os.Open(a.filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\t\terr = handler(line, model)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn scanner.Err()\n}\n\nfunc (a *Adapter) savePolicyFile(text string) error {\n\tf, err := os.Create(a.filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tw := bufio.NewWriter(f)\n\n\t_, err = w.WriteString(text)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = w.Flush()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn f.Close()\n}\n\n// AddPolicy adds a policy rule to the storage.\nfunc (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {\n\treturn errors.New(\"not implemented\")\n}\n\n// AddPolicies adds policy rules to the storage.\nfunc (a *Adapter) AddPolicies(sec string, ptype string, rules [][]string) error {\n\treturn errors.New(\"not implemented\")\n}\n\n// RemovePolicy removes a policy rule from the storage.\nfunc (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {\n\treturn errors.New(\"not implemented\")\n}\n\n// RemovePolicies removes policy rules from the storage.\nfunc (a *Adapter) RemovePolicies(sec string, ptype string, rules [][]string) error {\n\treturn errors.New(\"not implemented\")\n}\n\n// RemoveFilteredPolicy removes policy rules that match the filter from the storage.\nfunc (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\treturn errors.New(\"not implemented\")\n}\n"
  },
  {
    "path": "persist/file-adapter/adapter_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fileadapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\nfunc (a *Adapter) UpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.UpdatePolicy(sec, ptype, oldRule, newRule)\n}\n\nfunc (a *Adapter) UpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.UpdatePolicies(sec, ptype, oldRules, newRules)\n}\n\nfunc (a *Adapter) UpdateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error) {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn a.UpdateFilteredPolicies(sec, ptype, newRules, fieldIndex, fieldValues...)\n}\n\n// LoadPolicyCtx loads all policy rules from the storage with context.\nfunc (a *Adapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.LoadPolicy(model)\n}\n\n// SavePolicyCtx saves all policy rules to the storage with context.\nfunc (a *Adapter) SavePolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.SavePolicy(model)\n}\n\n// AddPolicyCtx adds a policy rule to the storage with context.\nfunc (a *Adapter) AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.AddPolicy(sec, ptype, rule)\n}\n\n// AddPoliciesCtx adds policy rules to the storage with context.\nfunc (a *Adapter) AddPoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.AddPolicies(sec, ptype, rules)\n}\n\n// RemovePolicyCtx removes a policy rule from the storage with context.\nfunc (a *Adapter) RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.RemovePolicy(sec, ptype, rule)\n}\n\n// RemovePoliciesCtx removes policy rules from the storage with context.\nfunc (a *Adapter) RemovePoliciesCtx(ctx context.Context, sec string, ptype string, rules [][]string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.RemovePolicies(sec, ptype, rules)\n}\n\n// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.\nfunc (a *Adapter) RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n}\n\nfunc checkCtx(ctx context.Context) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "persist/file-adapter/adapter_filtered.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fileadapter\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// FilteredAdapter is the filtered file adapter for Casbin. It can load policy\n// from file or save policy to file and supports loading of filtered policies.\ntype FilteredAdapter struct {\n\t*Adapter\n\tfiltered bool\n}\n\n// Filter defines the filtering rules for a FilteredAdapter's policy. Empty values\n// are ignored, but all others must match the filter.\ntype Filter struct {\n\tP  []string\n\tG  []string\n\tG1 []string\n\tG2 []string\n\tG3 []string\n\tG4 []string\n\tG5 []string\n}\n\n// NewFilteredAdapter is the constructor for FilteredAdapter.\nfunc NewFilteredAdapter(filePath string) *FilteredAdapter {\n\ta := FilteredAdapter{}\n\ta.filtered = true\n\ta.Adapter = NewAdapter(filePath)\n\treturn &a\n}\n\n// LoadPolicy loads all policy rules from the storage.\nfunc (a *FilteredAdapter) LoadPolicy(model model.Model) error {\n\ta.filtered = false\n\treturn a.Adapter.LoadPolicy(model)\n}\n\n// LoadFilteredPolicy loads only policy rules that match the filter.\nfunc (a *FilteredAdapter) LoadFilteredPolicy(model model.Model, filter interface{}) error {\n\tif filter == nil {\n\t\treturn a.LoadPolicy(model)\n\t}\n\tif a.filePath == \"\" {\n\t\treturn errors.New(\"invalid file path, file path cannot be empty\")\n\t}\n\n\tfilterValue, ok := filter.(*Filter)\n\tif !ok {\n\t\treturn errors.New(\"invalid filter type\")\n\t}\n\terr := a.loadFilteredPolicyFile(model, filterValue, persist.LoadPolicyLine)\n\tif err == nil {\n\t\ta.filtered = true\n\t}\n\treturn err\n}\n\nfunc (a *FilteredAdapter) loadFilteredPolicyFile(model model.Model, filter *Filter, handler func(string, model.Model) error) error {\n\tf, err := os.Open(a.filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tscanner := bufio.NewScanner(f)\n\tfor scanner.Scan() {\n\t\tline := strings.TrimSpace(scanner.Text())\n\n\t\tif filterLine(line, filter) {\n\t\t\tcontinue\n\t\t}\n\n\t\terr = handler(line, model)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn scanner.Err()\n}\n\n// IsFiltered returns true if the loaded policy has been filtered.\nfunc (a *FilteredAdapter) IsFiltered() bool {\n\treturn a.filtered\n}\n\n// SavePolicy saves all policy rules to the storage.\nfunc (a *FilteredAdapter) SavePolicy(model model.Model) error {\n\tif a.filtered {\n\t\treturn errors.New(\"cannot save a filtered policy\")\n\t}\n\treturn a.Adapter.SavePolicy(model)\n}\n\nfunc filterLine(line string, filter *Filter) bool {\n\tif filter == nil {\n\t\treturn false\n\t}\n\tp := strings.Split(line, \",\")\n\tif len(p) == 0 {\n\t\treturn true\n\t}\n\tvar filterSlice []string\n\tswitch strings.TrimSpace(p[0]) {\n\tcase \"p\":\n\t\tfilterSlice = filter.P\n\tcase \"g\":\n\t\tfilterSlice = filter.G\n\tcase \"g1\":\n\t\tfilterSlice = filter.G1\n\tcase \"g2\":\n\t\tfilterSlice = filter.G2\n\tcase \"g3\":\n\t\tfilterSlice = filter.G3\n\tcase \"g4\":\n\t\tfilterSlice = filter.G4\n\tcase \"g5\":\n\t\tfilterSlice = filter.G5\n\t}\n\treturn filterWords(p, filterSlice)\n}\n\nfunc filterWords(line []string, filter []string) bool {\n\tif len(line) < len(filter)+1 {\n\t\treturn true\n\t}\n\tvar skipLine bool\n\tfor i, v := range filter {\n\t\tif len(v) > 0 && strings.TrimSpace(v) != strings.TrimSpace(line[i+1]) {\n\t\t\tskipLine = true\n\t\t\tbreak\n\t\t}\n\t}\n\treturn skipLine\n}\n"
  },
  {
    "path": "persist/file-adapter/adapter_filtered_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fileadapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// LoadPolicyCtx loads all policy rules from the storage with context.\nfunc (a *FilteredAdapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.LoadPolicy(model)\n}\n\n// LoadFilteredPolicyCtx loads only policy rules that match the filter with context.\nfunc (a *FilteredAdapter) LoadFilteredPolicyCtx(ctx context.Context, model model.Model, filter interface{}) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.LoadFilteredPolicy(model, filter)\n}\n\n// SavePolicyCtx saves all policy rules to the storage with context.\nfunc (a *FilteredAdapter) SavePolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.SavePolicy(model)\n}\n\n// IsFilteredCtx returns true if the loaded policy has been filtered with context.\nfunc (a *FilteredAdapter) IsFilteredCtx(ctx context.Context) bool {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn false\n\t}\n\n\treturn a.IsFiltered()\n}\n"
  },
  {
    "path": "persist/file-adapter/adapter_mock.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage fileadapter\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// AdapterMock is the file adapter for Casbin.\n// It can load policy from file or save policy to file.\ntype AdapterMock struct {\n\tfilePath   string\n\terrorValue string\n}\n\n// NewAdapterMock is the constructor for AdapterMock.\nfunc NewAdapterMock(filePath string) *AdapterMock {\n\ta := AdapterMock{}\n\ta.filePath = filePath\n\treturn &a\n}\n\n// LoadPolicy loads all policy rules from the storage.\nfunc (a *AdapterMock) LoadPolicy(model model.Model) error {\n\terr := a.loadPolicyFile(model, persist.LoadPolicyLine)\n\treturn err\n}\n\n// SavePolicy saves all policy rules to the storage.\nfunc (a *AdapterMock) SavePolicy(model model.Model) error {\n\treturn nil\n}\n\nfunc (a *AdapterMock) loadPolicyFile(model model.Model, handler func(string, model.Model) error) error {\n\tf, err := os.Open(a.filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\tbuf := bufio.NewReader(f)\n\tfor {\n\t\tline, err := buf.ReadString('\\n')\n\t\tline = strings.TrimSpace(line)\n\t\tif err2 := handler(line, model); err2 != nil {\n\t\t\treturn err2\n\t\t}\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n}\n\n// SetMockErr sets string to be returned by of the mock during testing.\nfunc (a *AdapterMock) SetMockErr(errorToSet string) {\n\ta.errorValue = errorToSet\n}\n\n// GetMockErr returns a mock error or nil.\nfunc (a *AdapterMock) GetMockErr() error {\n\tvar returnError error\n\tif a.errorValue != \"\" {\n\t\treturnError = errors.New(a.errorValue)\n\t}\n\treturn returnError\n}\n\n// AddPolicy adds a policy rule to the storage.\nfunc (a *AdapterMock) AddPolicy(sec string, ptype string, rule []string) error {\n\treturn a.GetMockErr()\n}\n\n// AddPolicies removes policy rules from the storage.\nfunc (a *AdapterMock) AddPolicies(sec string, ptype string, rules [][]string) error {\n\treturn a.GetMockErr()\n}\n\n// RemovePolicy removes a policy rule from the storage.\nfunc (a *AdapterMock) RemovePolicy(sec string, ptype string, rule []string) error {\n\treturn a.GetMockErr()\n}\n\n// RemovePolicies removes policy rules from the storage.\nfunc (a *AdapterMock) RemovePolicies(sec string, ptype string, rules [][]string) error {\n\treturn a.GetMockErr()\n}\n\n// UpdatePolicy removes a policy rule from the storage.\nfunc (a *AdapterMock) UpdatePolicy(sec string, ptype string, oldRule, newPolicy []string) error {\n\treturn a.GetMockErr()\n}\n\nfunc (a *AdapterMock) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error {\n\treturn a.GetMockErr()\n}\n\n// RemoveFilteredPolicy removes policy rules that match the filter from the storage.\nfunc (a *AdapterMock) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\treturn a.GetMockErr()\n}\n"
  },
  {
    "path": "persist/persist_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\nfunc TestPersist(t *testing.T) {\n\t// No tests yet\n}\n\nfunc testRuleCount(t *testing.T, model model.Model, expected int, sec string, ptype string, tag string) {\n\tt.Helper()\n\n\truleCount := len(model[sec][ptype].Policy)\n\tif ruleCount != expected {\n\t\tt.Errorf(\"[%s] rule count: %d, expected %d\", tag, ruleCount, expected)\n\t}\n}\n\nfunc TestDuplicateRuleInAdapter(t *testing.T) {\n\te, _ := casbin.NewEnforcer(\"../examples/basic_model.conf\")\n\n\t_, _ = e.AddPolicy(\"alice\", \"data1\", \"read\")\n\t_, _ = e.AddPolicy(\"alice\", \"data1\", \"read\")\n\n\ttestRuleCount(t, e.GetModel(), 1, \"p\", \"p\", \"AddPolicy\")\n\n\te.ClearPolicy()\n\n\t// simulate adapter.LoadPolicy with duplicate rules\n\t_ = persist.LoadPolicyArray([]string{\"p\", \"alice\", \"data1\", \"read\"}, e.GetModel())\n\t_ = persist.LoadPolicyArray([]string{\"p\", \"alice\", \"data1\", \"read\"}, e.GetModel())\n\n\ttestRuleCount(t, e.GetModel(), 1, \"p\", \"p\", \"LoadPolicyArray\")\n}\n"
  },
  {
    "path": "persist/string-adapter/adapter.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage stringadapter\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// Adapter is the string adapter for Casbin.\n// It can load policy from string or save policy to string.\ntype Adapter struct {\n\tLine string\n}\n\n// NewAdapter is the constructor for Adapter.\nfunc NewAdapter(line string) *Adapter {\n\treturn &Adapter{\n\t\tLine: line,\n\t}\n}\n\n// LoadPolicy loads all policy rules from the storage.\nfunc (a *Adapter) LoadPolicy(model model.Model) error {\n\tif a.Line == \"\" {\n\t\treturn errors.New(\"invalid line, line cannot be empty\")\n\t}\n\tstrs := strings.Split(a.Line, \"\\n\")\n\tfor _, str := range strs {\n\t\tif str == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t_ = persist.LoadPolicyLine(str, model)\n\t}\n\n\treturn nil\n}\n\n// SavePolicy saves all policy rules to the storage.\nfunc (a *Adapter) SavePolicy(model model.Model) error {\n\tvar tmp bytes.Buffer\n\tfor ptype, ast := range model[\"p\"] {\n\t\tfor _, rule := range ast.Policy {\n\t\t\ttmp.WriteString(ptype + \", \")\n\t\t\ttmp.WriteString(util.ArrayToString(rule))\n\t\t\ttmp.WriteString(\"\\n\")\n\t\t}\n\t}\n\n\tfor ptype, ast := range model[\"g\"] {\n\t\tfor _, rule := range ast.Policy {\n\t\t\ttmp.WriteString(ptype + \", \")\n\t\t\ttmp.WriteString(util.ArrayToString(rule))\n\t\t\ttmp.WriteString(\"\\n\")\n\t\t}\n\t}\n\ta.Line = strings.TrimRight(tmp.String(), \"\\n\")\n\treturn nil\n}\n\n// AddPolicy adds a policy rule to the storage.\nfunc (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {\n\treturn errors.New(\"not implemented\")\n}\n\n// RemovePolicy removes a policy rule from the storage.\nfunc (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {\n\ta.Line = \"\"\n\treturn nil\n}\n\n// RemoveFilteredPolicy removes policy rules that match the filter from the storage.\nfunc (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\treturn errors.New(\"not implemented\")\n}\n"
  },
  {
    "path": "persist/string-adapter/adapter_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage stringadapter\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// LoadPolicyCtx loads all policy rules from the storage with context.\nfunc (a *Adapter) LoadPolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.LoadPolicy(model)\n}\n\n// SavePolicyCtx saves all policy rules to the storage with context.\nfunc (a *Adapter) SavePolicyCtx(ctx context.Context, model model.Model) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.SavePolicy(model)\n}\n\n// AddPolicyCtx adds a policy rule to the storage with context.\nfunc (a *Adapter) AddPolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.AddPolicy(sec, ptype, rule)\n}\n\n// RemovePolicyCtx removes a policy rule from the storage with context.\nfunc (a *Adapter) RemovePolicyCtx(ctx context.Context, sec string, ptype string, rule []string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.RemovePolicy(sec, ptype, rule)\n}\n\n// RemoveFilteredPolicyCtx removes policy rules that match the filter from the storage with context.\nfunc (a *Adapter) RemoveFilteredPolicyCtx(ctx context.Context, sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\tif err := checkCtx(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.RemoveFilteredPolicy(sec, ptype, fieldIndex, fieldValues...)\n}\n\nfunc checkCtx(ctx context.Context) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "persist/string-adapter/adapter_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage stringadapter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3\"\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\nfunc Test_KeyMatchRbac(t *testing.T) {\n\tconf := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _ , _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub)  && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)\n`\n\tline := `\np, alice, /alice_data/*, (GET)|(POST)\np, alice, /alice_data/resource1, POST\np, data_group_admin, /admin/*, POST\np, data_group_admin, /bob_data/*, POST\ng, alice, data_group_admin\n`\n\ta := NewAdapter(line)\n\tm := model.NewModel()\n\terr := m.LoadModelFromText(conf)\n\tif err != nil {\n\t\tt.Errorf(\"load model from text failed: %v\", err.Error())\n\t\treturn\n\t}\n\te, _ := casbin.NewEnforcer(m, a)\n\tsub := \"alice\"\n\tobj := \"/alice_data/login\"\n\tact := \"POST\"\n\tif res, _ := e.Enforce(sub, obj, act); !res {\n\t\tt.Error(\"unexpected enforce result\")\n\t}\n}\n\nfunc Test_StringRbac(t *testing.T) {\n\tconf := `\n[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _ , _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act\n`\n\tline := `\np, alice, data1, read\np, data_group_admin, data3, read\np, data_group_admin, data3, write\ng, alice, data_group_admin\n`\n\ta := NewAdapter(line)\n\tm := model.NewModel()\n\terr := m.LoadModelFromText(conf)\n\tif err != nil {\n\t\tt.Errorf(\"load model from text failed: %v\", err.Error())\n\t\treturn\n\t}\n\te, _ := casbin.NewEnforcer(m, a)\n\tsub := \"alice\" // the user that wants to access a resource.\n\tobj := \"data1\" // the resource that is going to be accessed.\n\tact := \"read\"  // the operation that the user performs on the resource.\n\tif res, _ := e.Enforce(sub, obj, act); !res {\n\t\tt.Error(\"unexpected enforce result\")\n\t}\n}\n"
  },
  {
    "path": "persist/transaction.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport \"context\"\n\n// TransactionalAdapter defines the interface for adapters that support transactions.\n// Adapters implementing this interface can participate in Casbin transactions.\ntype TransactionalAdapter interface {\n\tAdapter\n\t// BeginTransaction starts a new transaction and returns a transaction context.\n\tBeginTransaction(ctx context.Context) (TransactionContext, error)\n}\n\n// TransactionContext represents a database transaction context.\n// It provides methods to commit or rollback the transaction and get an adapter\n// that operates within this transaction.\ntype TransactionContext interface {\n\t// Commit commits the transaction.\n\tCommit() error\n\t// Rollback rolls back the transaction.\n\tRollback() error\n\t// GetAdapter returns an adapter that operates within this transaction.\n\tGetAdapter() Adapter\n}\n\n// PolicyOperation represents a policy operation that can be buffered in a transaction.\ntype PolicyOperation struct {\n\tType       OperationType // The type of operation (add, remove, update)\n\tSection    string        // The section of the policy (p, g)\n\tPolicyType string        // The policy type (p, p2, g, g2, etc.)\n\tRules      [][]string    // The policy rules to operate on\n\tOldRules   [][]string    // For update operations, the old rules to replace\n}\n\n// OperationType represents the type of policy operation.\ntype OperationType int\n\nconst (\n\t// OperationAdd represents adding policy rules.\n\tOperationAdd OperationType = iota\n\t// OperationRemove represents removing policy rules.\n\tOperationRemove\n\t// OperationUpdate represents updating policy rules.\n\tOperationUpdate\n)\n"
  },
  {
    "path": "persist/update_adapter.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\n// UpdatableAdapter is the interface for Casbin adapters with add update policy function.\ntype UpdatableAdapter interface {\n\tAdapter\n\t// UpdatePolicy updates a policy rule from storage.\n\t// This is part of the Auto-Save feature.\n\tUpdatePolicy(sec string, ptype string, oldRule, newRule []string) error\n\t// UpdatePolicies updates some policy rules to storage, like db, redis.\n\tUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error\n\t// UpdateFilteredPolicies deletes old rules and adds new rules.\n\tUpdateFilteredPolicies(sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error)\n}\n"
  },
  {
    "path": "persist/update_adapter_context.go",
    "content": "// Copyright 2024 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport \"context\"\n\n// ContextUpdatableAdapter is the context-aware interface for Casbin adapters with add update policy function.\ntype ContextUpdatableAdapter interface {\n\tContextAdapter\n\n\t// UpdatePolicyCtx updates a policy rule from storage.\n\t// This is part of the Auto-Save feature.\n\tUpdatePolicyCtx(ctx context.Context, sec string, ptype string, oldRule, newRule []string) error\n\t// UpdatePoliciesCtx updates some policy rules to storage, like db, redis.\n\tUpdatePoliciesCtx(ctx context.Context, sec string, ptype string, oldRules, newRules [][]string) error\n\t// UpdateFilteredPoliciesCtx deletes old rules and adds new rules.\n\tUpdateFilteredPoliciesCtx(ctx context.Context, sec string, ptype string, newRules [][]string, fieldIndex int, fieldValues ...string) ([][]string, error)\n}\n"
  },
  {
    "path": "persist/watcher.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\n// Watcher is the interface for Casbin watchers.\ntype Watcher interface {\n\t// SetUpdateCallback sets the callback function that the watcher will call\n\t// when the policy in DB has been changed by other instances.\n\t// A classic callback is Enforcer.LoadPolicy().\n\tSetUpdateCallback(func(string)) error\n\t// Update calls the update callback of other instances to synchronize their policy.\n\t// It is usually called after changing the policy in DB, like Enforcer.SavePolicy(),\n\t// Enforcer.AddPolicy(), Enforcer.RemovePolicy(), etc.\n\tUpdate() error\n\t// Close stops and releases the watcher, the callback function will not be called any more.\n\tClose()\n}\n"
  },
  {
    "path": "persist/watcher_ex.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\nimport \"github.com/casbin/casbin/v3/model\"\n\n// WatcherEx is the strengthened Casbin watchers.\ntype WatcherEx interface {\n\tWatcher\n\t// UpdateForAddPolicy calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.AddPolicy()\n\tUpdateForAddPolicy(sec, ptype string, params ...string) error\n\t// UpdateForRemovePolicy calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.RemovePolicy()\n\tUpdateForRemovePolicy(sec, ptype string, params ...string) error\n\t// UpdateForRemoveFilteredPolicy calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.RemoveFilteredNamedGroupingPolicy()\n\tUpdateForRemoveFilteredPolicy(sec, ptype string, fieldIndex int, fieldValues ...string) error\n\t// UpdateForSavePolicy calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.RemoveFilteredNamedGroupingPolicy()\n\tUpdateForSavePolicy(model model.Model) error\n\t// UpdateForAddPolicies calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.AddPolicies()\n\tUpdateForAddPolicies(sec string, ptype string, rules ...[]string) error\n\t// UpdateForRemovePolicies calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.RemovePolicies()\n\tUpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error\n}\n"
  },
  {
    "path": "persist/watcher_update.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage persist\n\n// UpdatableWatcher is strengthened for Casbin watchers.\ntype UpdatableWatcher interface {\n\tWatcher\n\t// UpdateForUpdatePolicy calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.UpdatePolicy()\n\tUpdateForUpdatePolicy(sec string, ptype string, oldRule, newRule []string) error\n\t// UpdateForUpdatePolicies calls the update callback of other instances to synchronize their policy.\n\t// It is called after Enforcer.UpdatePolicies()\n\tUpdateForUpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) error\n}\n"
  },
  {
    "path": "rbac/context_role_manager.go",
    "content": "// Copyright 2023 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage rbac\n\nimport \"context\"\n\n// ContextRoleManager provides a context-aware interface to define the operations for managing roles.\n// Prefer this over RoleManager interface for context propagation, which is useful for things like handling\n// request timeouts.\ntype ContextRoleManager interface {\n\tRoleManager\n\n\t// ClearCtx clears all stored data and resets the role manager to the initial state with context.\n\tClearCtx(ctx context.Context) error\n\t// AddLinkCtx adds the inheritance link between two roles. role: name1 and role: name2 with context.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tAddLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error\n\t// DeleteLinkCtx deletes the inheritance link between two roles. role: name1 and role: name2 with context.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tDeleteLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) error\n\t// HasLinkCtx determines whether a link exists between two roles. role: name1 inherits role: name2 with context.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tHasLinkCtx(ctx context.Context, name1 string, name2 string, domain ...string) (bool, error)\n\t// GetRolesCtx gets the roles that a user inherits with context.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tGetRolesCtx(ctx context.Context, name string, domain ...string) ([]string, error)\n\t// GetUsersCtx gets the users that inherits a role with context.\n\t// domain is a prefix to the users (can be used for other purposes).\n\tGetUsersCtx(ctx context.Context, name string, domain ...string) ([]string, error)\n\t// GetDomainsCtx gets domains that a user has with context.\n\tGetDomainsCtx(ctx context.Context, name string) ([]string, error)\n\t// GetAllDomainsCtx gets all domains with context.\n\tGetAllDomainsCtx(ctx context.Context) ([]string, error)\n}\n"
  },
  {
    "path": "rbac/default-role-manager/role_manager.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage defaultrolemanager\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nconst defaultDomain string = \"\"\n\n// Role represents the data structure for a role in RBAC.\ntype Role struct {\n\tname                       string\n\troles                      *sync.Map\n\tusers                      *sync.Map\n\tmatched                    *sync.Map\n\tmatchedBy                  *sync.Map\n\tlinkConditionFuncMap       *sync.Map\n\tlinkConditionFuncParamsMap *sync.Map\n}\n\nfunc newRole(name string) *Role {\n\tr := Role{}\n\tr.name = name\n\tr.roles = &sync.Map{}\n\tr.users = &sync.Map{}\n\tr.matched = &sync.Map{}\n\tr.matchedBy = &sync.Map{}\n\tr.linkConditionFuncMap = &sync.Map{}\n\tr.linkConditionFuncParamsMap = &sync.Map{}\n\treturn &r\n}\n\nfunc (r *Role) addRole(role *Role) {\n\tr.roles.Store(role.name, role)\n\trole.addUser(r)\n}\n\nfunc (r *Role) removeRole(role *Role) {\n\tr.roles.Delete(role.name)\n\trole.removeUser(r)\n}\n\n// should only be called inside addRole.\nfunc (r *Role) addUser(user *Role) {\n\tr.users.Store(user.name, user)\n}\n\n// should only be called inside removeRole.\nfunc (r *Role) removeUser(user *Role) {\n\tr.users.Delete(user.name)\n}\n\nfunc (r *Role) addMatch(role *Role) {\n\tr.matched.Store(role.name, role)\n\trole.matchedBy.Store(r.name, r)\n}\n\nfunc (r *Role) removeMatch(role *Role) {\n\tr.matched.Delete(role.name)\n\trole.matchedBy.Delete(r.name)\n}\n\nfunc (r *Role) removeMatches() {\n\tr.matched.Range(func(key, value interface{}) bool {\n\t\tr.removeMatch(value.(*Role))\n\t\treturn true\n\t})\n\tr.matchedBy.Range(func(key, value interface{}) bool {\n\t\tvalue.(*Role).removeMatch(r)\n\t\treturn true\n\t})\n}\n\nfunc (r *Role) rangeRoles(fn func(key, value interface{}) bool) {\n\tr.roles.Range(fn)\n\tr.roles.Range(func(key, value interface{}) bool {\n\t\trole := value.(*Role)\n\t\trole.matched.Range(fn)\n\t\treturn true\n\t})\n\tr.matchedBy.Range(func(key, value interface{}) bool {\n\t\trole := value.(*Role)\n\t\trole.roles.Range(fn)\n\t\treturn true\n\t})\n}\n\nfunc (r *Role) rangeUsers(fn func(key, value interface{}) bool) {\n\tr.users.Range(fn)\n\tr.users.Range(func(key, value interface{}) bool {\n\t\trole := value.(*Role)\n\t\trole.matched.Range(fn)\n\t\treturn true\n\t})\n\tr.matchedBy.Range(func(key, value interface{}) bool {\n\t\trole := value.(*Role)\n\t\trole.users.Range(fn)\n\t\treturn true\n\t})\n}\n\nfunc (r *Role) getRoles() []string {\n\tvar names []string\n\tr.rangeRoles(func(key, value interface{}) bool {\n\t\tnames = append(names, key.(string))\n\t\treturn true\n\t})\n\treturn util.RemoveDuplicateElement(names)\n}\n\nfunc (r *Role) getUsers() []string {\n\tvar names []string\n\tr.rangeUsers(func(key, value interface{}) bool {\n\t\tnames = append(names, key.(string))\n\t\treturn true\n\t})\n\treturn names\n}\n\ntype linkConditionFuncKey struct {\n\troleName   string\n\tdomainName string\n}\n\nfunc (r *Role) addLinkConditionFunc(role *Role, domain string, fn rbac.LinkConditionFunc) {\n\tr.linkConditionFuncMap.Store(linkConditionFuncKey{role.name, domain}, fn)\n}\n\nfunc (r *Role) getLinkConditionFunc(role *Role, domain string) (rbac.LinkConditionFunc, bool) {\n\tfn, ok := r.linkConditionFuncMap.Load(linkConditionFuncKey{role.name, domain})\n\tif fn == nil {\n\t\treturn nil, ok\n\t}\n\treturn fn.(rbac.LinkConditionFunc), ok\n}\n\nfunc (r *Role) setLinkConditionFuncParams(role *Role, domain string, params ...string) {\n\tr.linkConditionFuncParamsMap.Store(linkConditionFuncKey{role.name, domain}, params)\n}\n\nfunc (r *Role) getLinkConditionFuncParams(role *Role, domain string) ([]string, bool) {\n\tparams, ok := r.linkConditionFuncParamsMap.Load(linkConditionFuncKey{role.name, domain})\n\tif params == nil {\n\t\treturn nil, ok\n\t}\n\treturn params.([]string), ok\n}\n\n// RoleManagerImpl provides a default implementation for the RoleManager interface.\ntype RoleManagerImpl struct {\n\tallRoles           *sync.Map\n\tmaxHierarchyLevel  int\n\tmatchingFunc       rbac.MatchingFunc\n\tdomainMatchingFunc rbac.MatchingFunc\n\tmatchingFuncCache  *util.SyncLRUCache\n\tmutex              sync.Mutex\n}\n\n// NewRoleManagerImpl is the constructor for creating an instance of the\n// default RoleManager implementation.\nfunc NewRoleManagerImpl(maxHierarchyLevel int) *RoleManagerImpl {\n\trm := RoleManagerImpl{}\n\t_ = rm.Clear() // init allRoles and matchingFuncCache\n\trm.maxHierarchyLevel = maxHierarchyLevel\n\treturn &rm\n}\n\n// use this constructor to avoid rebuild of AddMatchingFunc.\nfunc newRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *RoleManagerImpl {\n\trm := NewRoleManagerImpl(maxHierarchyLevel)\n\trm.matchingFunc = fn\n\treturn rm\n}\n\n// rebuilds role cache.\nfunc (rm *RoleManagerImpl) rebuild() {\n\troles := rm.allRoles\n\t_ = rm.Clear()\n\trangeLinks(roles, func(name1, name2 string, domain ...string) bool {\n\t\t_ = rm.AddLink(name1, name2, domain...)\n\t\treturn true\n\t})\n}\n\nfunc (rm *RoleManagerImpl) Match(str string, pattern string) bool {\n\tif str == pattern {\n\t\treturn true\n\t}\n\n\tif rm.matchingFunc != nil {\n\t\treturn rm.matchingFunc(str, pattern)\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc (rm *RoleManagerImpl) rangeMatchingRoles(name string, isPattern bool, fn func(role *Role) bool) {\n\trm.allRoles.Range(func(key, value interface{}) bool {\n\t\tname2 := key.(string)\n\t\tif isPattern && name != name2 && rm.Match(name2, name) {\n\t\t\tfn(value.(*Role))\n\t\t} else if !isPattern && name != name2 && rm.Match(name, name2) {\n\t\t\tfn(value.(*Role))\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc (rm *RoleManagerImpl) load(name interface{}) (value *Role, ok bool) {\n\tif r, ok := rm.allRoles.Load(name); ok {\n\t\treturn r.(*Role), true\n\t}\n\treturn nil, false\n}\n\n// loads or creates a role.\nfunc (rm *RoleManagerImpl) getRole(name string) (r *Role, created bool) {\n\tvar role *Role\n\tvar ok bool\n\n\tif role, ok = rm.load(name); !ok {\n\t\trole = newRole(name)\n\t\trm.allRoles.Store(name, role)\n\n\t\tif rm.matchingFunc != nil {\n\t\t\trm.rangeMatchingRoles(name, false, func(r *Role) bool {\n\t\t\t\tr.addMatch(role)\n\t\t\t\treturn true\n\t\t\t})\n\n\t\t\trm.rangeMatchingRoles(name, true, func(r *Role) bool {\n\t\t\t\trole.addMatch(r)\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\treturn role, !ok\n}\n\nfunc loadAndDelete(m *sync.Map, name string) (value interface{}, loaded bool) {\n\tvalue, loaded = m.Load(name)\n\tif loaded {\n\t\tm.Delete(name)\n\t}\n\treturn value, loaded\n}\n\nfunc (rm *RoleManagerImpl) removeRole(name string) {\n\tif role, ok := loadAndDelete(rm.allRoles, name); ok {\n\t\trole.(*Role).removeMatches()\n\t}\n}\n\n// AddMatchingFunc support use pattern in g.\nfunc (rm *RoleManagerImpl) AddMatchingFunc(name string, fn rbac.MatchingFunc) {\n\trm.matchingFunc = fn\n\trm.rebuild()\n}\n\n// AddDomainMatchingFunc support use domain pattern in g.\nfunc (rm *RoleManagerImpl) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {\n\trm.domainMatchingFunc = fn\n}\n\n// Clear clears all stored data and resets the role manager to the initial state.\nfunc (rm *RoleManagerImpl) Clear() error {\n\trm.matchingFuncCache = util.NewSyncLRUCache(100)\n\trm.allRoles = &sync.Map{}\n\treturn nil\n}\n\n// AddLink adds the inheritance link between role: name1 and role: name2.\n// aka role: name1 inherits role: name2.\nfunc (rm *RoleManagerImpl) AddLink(name1 string, name2 string, domains ...string) error {\n\tuser, _ := rm.getRole(name1)\n\trole, _ := rm.getRole(name2)\n\tuser.addRole(role)\n\treturn nil\n}\n\n// DeleteLink deletes the inheritance link between role: name1 and role: name2.\n// aka role: name1 does not inherit role: name2 any more.\nfunc (rm *RoleManagerImpl) DeleteLink(name1 string, name2 string, domains ...string) error {\n\tuser, _ := rm.getRole(name1)\n\trole, _ := rm.getRole(name2)\n\tuser.removeRole(role)\n\treturn nil\n}\n\n// HasLink determines whether role: name1 inherits role: name2.\nfunc (rm *RoleManagerImpl) HasLink(name1 string, name2 string, domains ...string) (bool, error) {\n\tif name1 == name2 || (rm.matchingFunc != nil && rm.Match(name1, name2)) {\n\t\treturn true, nil\n\t}\n\n\t// Lock to prevent race conditions between getRole and removeRole\n\trm.mutex.Lock()\n\tdefer rm.mutex.Unlock()\n\n\tuser, userCreated := rm.getRole(name1)\n\trole, roleCreated := rm.getRole(name2)\n\n\tif userCreated {\n\t\tdefer rm.removeRole(user.name)\n\t}\n\tif roleCreated {\n\t\tdefer rm.removeRole(role.name)\n\t}\n\n\treturn rm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, rm.maxHierarchyLevel), nil\n}\n\nfunc (rm *RoleManagerImpl) hasLinkHelper(targetName string, roles map[string]*Role, level int) bool {\n\tif level < 0 || len(roles) == 0 {\n\t\treturn false\n\t}\n\n\tnextRoles := map[string]*Role{}\n\tfor _, role := range roles {\n\t\tif targetName == role.name || (rm.matchingFunc != nil && rm.Match(role.name, targetName)) {\n\t\t\treturn true\n\t\t}\n\t\trole.rangeRoles(func(key, value interface{}) bool {\n\t\t\tnextRoles[key.(string)] = value.(*Role)\n\t\t\treturn true\n\t\t})\n\t}\n\n\treturn rm.hasLinkHelper(targetName, nextRoles, level-1)\n}\n\n// GetRoles gets the roles that a user inherits.\nfunc (rm *RoleManagerImpl) GetRoles(name string, domains ...string) ([]string, error) {\n\tuser, created := rm.getRole(name)\n\tif created {\n\t\tdefer rm.removeRole(user.name)\n\t}\n\treturn user.getRoles(), nil\n}\n\n// GetUsers gets the users of a role.\n// domain is an unreferenced parameter here, may be used in other implementations.\nfunc (rm *RoleManagerImpl) GetUsers(name string, domain ...string) ([]string, error) {\n\trole, created := rm.getRole(name)\n\tif created {\n\t\tdefer rm.removeRole(role.name)\n\t}\n\treturn role.getUsers(), nil\n}\n\n// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.\nfunc (rm *RoleManagerImpl) GetImplicitRoles(name string, domain ...string) ([]string, error) {\n\tuser, created := rm.getRole(name)\n\tif created {\n\t\tdefer rm.removeRole(user.name)\n\t}\n\n\tvar res []string\n\troleSet := make(map[string]bool)\n\troleSet[name] = true\n\troles := map[string]*Role{user.name: user}\n\n\treturn rm.getImplicitRolesHelper(roles, roleSet, res, 0), nil\n}\n\n// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.\nfunc (rm *RoleManagerImpl) GetImplicitUsers(name string, domain ...string) ([]string, error) {\n\trole, created := rm.getRole(name)\n\tif created {\n\t\tdefer rm.removeRole(role.name)\n\t}\n\n\tvar res []string\n\tuserSet := make(map[string]bool)\n\tuserSet[name] = true\n\tusers := map[string]*Role{role.name: role}\n\n\treturn rm.getImplicitUsersHelper(users, userSet, res, 0), nil\n}\n\n// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel.\nfunc (rm *RoleManagerImpl) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int) []string {\n\tif level >= rm.maxHierarchyLevel || len(roles) == 0 {\n\t\treturn res\n\t}\n\n\tnextRoles := map[string]*Role{}\n\tfor _, role := range roles {\n\t\trole.rangeRoles(func(key, value interface{}) bool {\n\t\t\troleName := key.(string)\n\t\t\tif _, ok := roleSet[roleName]; !ok {\n\t\t\t\tres = append(res, roleName)\n\t\t\t\troleSet[roleName] = true\n\t\t\t\tnextRoles[roleName] = value.(*Role)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\n\treturn rm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1)\n}\n\n// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel.\nfunc (rm *RoleManagerImpl) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int) []string {\n\tif level >= rm.maxHierarchyLevel || len(users) == 0 {\n\t\treturn res\n\t}\n\n\tnextUsers := map[string]*Role{}\n\tfor _, user := range users {\n\t\tuser.rangeUsers(func(key, value interface{}) bool {\n\t\t\tuserName := key.(string)\n\t\t\tif _, ok := userSet[userName]; !ok {\n\t\t\t\tres = append(res, userName)\n\t\t\t\tuserSet[userName] = true\n\t\t\t\tnextUsers[userName] = value.(*Role)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\n\treturn rm.getImplicitUsersHelper(nextUsers, userSet, res, level+1)\n}\n\n// PrintRoles prints all the roles to log.\nfunc (rm *RoleManagerImpl) PrintRoles() error {\n\t// Logger has been removed - this is now a no-op\n\treturn nil\n}\n\n// GetDomains gets domains that a user has.\nfunc (rm *RoleManagerImpl) GetDomains(name string) ([]string, error) {\n\tdomains := []string{defaultDomain}\n\treturn domains, nil\n}\n\n// GetAllDomains gets all domains.\nfunc (rm *RoleManagerImpl) GetAllDomains() ([]string, error) {\n\tdomains := []string{defaultDomain}\n\treturn domains, nil\n}\n\nfunc (rm *RoleManagerImpl) copyFrom(other *RoleManagerImpl) {\n\tother.Range(func(name1, name2 string, domain ...string) bool {\n\t\t_ = rm.AddLink(name1, name2, domain...)\n\t\treturn true\n\t})\n}\n\nfunc rangeLinks(users *sync.Map, fn func(name1, name2 string, domain ...string) bool) {\n\tusers.Range(func(_, value interface{}) bool {\n\t\tuser := value.(*Role)\n\t\tuser.roles.Range(func(key, _ interface{}) bool {\n\t\t\troleName := key.(string)\n\t\t\treturn fn(user.name, roleName, defaultDomain)\n\t\t})\n\t\treturn true\n\t})\n}\n\nfunc (rm *RoleManagerImpl) Range(fn func(name1, name2 string, domain ...string) bool) {\n\trangeLinks(rm.allRoles, fn)\n}\n\n// Deprecated: BuildRelationship is no longer required.\nfunc (rm *RoleManagerImpl) BuildRelationship(name1 string, name2 string, domain ...string) error {\n\treturn nil\n}\n\ntype DomainManager struct {\n\trmMap              *sync.Map\n\tmaxHierarchyLevel  int\n\tmatchingFunc       rbac.MatchingFunc\n\tdomainMatchingFunc rbac.MatchingFunc\n\tmatchingFuncCache  *util.SyncLRUCache\n}\n\n// NewDomainManager is the constructor for creating an instance of the\n// default DomainManager implementation.\nfunc NewDomainManager(maxHierarchyLevel int) *DomainManager {\n\tdm := &DomainManager{}\n\t_ = dm.Clear() // init rmMap and rmCache\n\tdm.maxHierarchyLevel = maxHierarchyLevel\n\treturn dm\n}\n\n// AddMatchingFunc support use pattern in g.\nfunc (dm *DomainManager) AddMatchingFunc(name string, fn rbac.MatchingFunc) {\n\tdm.matchingFunc = fn\n\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*RoleManagerImpl).AddMatchingFunc(name, fn)\n\t\treturn true\n\t})\n}\n\n// AddDomainMatchingFunc support use domain pattern in g.\nfunc (dm *DomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {\n\tdm.domainMatchingFunc = fn\n\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*RoleManagerImpl).AddDomainMatchingFunc(name, fn)\n\t\treturn true\n\t})\n\tdm.rebuild()\n}\n\n// clears the map of RoleManagers.\nfunc (dm *DomainManager) rebuild() {\n\trmMap := dm.rmMap\n\t_ = dm.Clear()\n\trmMap.Range(func(key, value interface{}) bool {\n\t\tdomain := key.(string)\n\t\trm := value.(*RoleManagerImpl)\n\n\t\trm.Range(func(name1, name2 string, _ ...string) bool {\n\t\t\t_ = dm.AddLink(name1, name2, domain)\n\t\t\treturn true\n\t\t})\n\t\treturn true\n\t})\n}\n\n// Clear clears all stored data and resets the role manager to the initial state.\nfunc (dm *DomainManager) Clear() error {\n\tdm.rmMap = &sync.Map{}\n\tdm.matchingFuncCache = util.NewSyncLRUCache(100)\n\treturn nil\n}\n\nfunc (dm *DomainManager) getDomain(domains ...string) (domain string, err error) {\n\tswitch len(domains) {\n\tcase 0:\n\t\treturn defaultDomain, nil\n\tdefault:\n\t\treturn domains[0], nil\n\t}\n}\n\nfunc (dm *DomainManager) Match(str string, pattern string) bool {\n\tif str == pattern {\n\t\treturn true\n\t}\n\n\tif dm.domainMatchingFunc != nil {\n\t\treturn dm.domainMatchingFunc(str, pattern)\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc (dm *DomainManager) rangeAffectedRoleManagers(domain string, fn func(rm *RoleManagerImpl)) {\n\tif dm.domainMatchingFunc != nil {\n\t\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\t\tdomain2 := key.(string)\n\t\t\tif domain != domain2 && dm.Match(domain2, domain) {\n\t\t\t\tfn(value.(*RoleManagerImpl))\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\nfunc (dm *DomainManager) load(name interface{}) (value *RoleManagerImpl, ok bool) {\n\tif r, ok := dm.rmMap.Load(name); ok {\n\t\treturn r.(*RoleManagerImpl), true\n\t}\n\treturn nil, false\n}\n\n// load or create a RoleManager instance of domain.\nfunc (dm *DomainManager) getRoleManager(domain string, store bool) *RoleManagerImpl {\n\tvar rm *RoleManagerImpl\n\tvar ok bool\n\n\tif rm, ok = dm.load(domain); !ok {\n\t\trm = newRoleManagerWithMatchingFunc(dm.maxHierarchyLevel, dm.matchingFunc)\n\t\tif store {\n\t\t\tdm.rmMap.Store(domain, rm)\n\t\t}\n\t\tif dm.domainMatchingFunc != nil {\n\t\t\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\t\t\tdomain2 := key.(string)\n\t\t\t\trm2 := value.(*RoleManagerImpl)\n\t\t\t\tif domain != domain2 && dm.Match(domain, domain2) {\n\t\t\t\t\trm.copyFrom(rm2)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\treturn rm\n}\n\n// AddLink adds the inheritance link between role: name1 and role: name2.\n// aka role: name1 inherits role: name2.\nfunc (dm *DomainManager) AddLink(name1 string, name2 string, domains ...string) error {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn err\n\t}\n\troleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist\n\t_ = roleManager.AddLink(name1, name2, domains...)\n\n\tdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {\n\t\t_ = rm.AddLink(name1, name2, domains...)\n\t})\n\treturn nil\n}\n\n// DeleteLink deletes the inheritance link between role: name1 and role: name2.\n// aka role: name1 does not inherit role: name2 any more.\nfunc (dm *DomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn err\n\t}\n\troleManager := dm.getRoleManager(domain, true) // create role manager if it does not exist\n\t_ = roleManager.DeleteLink(name1, name2, domains...)\n\n\tdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {\n\t\t_ = rm.DeleteLink(name1, name2, domains...)\n\t})\n\treturn nil\n}\n\n// HasLink determines whether role: name1 inherits role: name2.\nfunc (dm *DomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\trm := dm.getRoleManager(domain, false)\n\treturn rm.HasLink(name1, name2, domains...)\n}\n\n// GetRoles gets the roles that a subject inherits.\nfunc (dm *DomainManager) GetRoles(name string, domains ...string) ([]string, error) {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trm := dm.getRoleManager(domain, false)\n\treturn rm.GetRoles(name, domains...)\n}\n\n// GetUsers gets the users of a role.\nfunc (dm *DomainManager) GetUsers(name string, domains ...string) ([]string, error) {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trm := dm.getRoleManager(domain, false)\n\treturn rm.GetUsers(name, domains...)\n}\n\n// GetImplicitRoles gets the implicit roles that a subject inherits, respecting maxHierarchyLevel.\nfunc (dm *DomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trm := dm.getRoleManager(domain, false)\n\treturn rm.GetImplicitRoles(name, domains...)\n}\n\n// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.\nfunc (dm *DomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {\n\tdomain, err := dm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trm := dm.getRoleManager(domain, false)\n\treturn rm.GetImplicitUsers(name, domains...)\n}\n\n// PrintRoles prints all the roles to log.\nfunc (dm *DomainManager) PrintRoles() error {\n\t// Logger has been removed - this is now a no-op\n\treturn nil\n}\n\n// GetDomains gets domains that a user has.\nfunc (dm *DomainManager) GetDomains(name string) ([]string, error) {\n\tvar domains []string\n\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tdomain := key.(string)\n\t\trm := value.(*RoleManagerImpl)\n\t\trole, created := rm.getRole(name)\n\t\tif created {\n\t\t\tdefer rm.removeRole(role.name)\n\t\t}\n\t\tif len(role.getUsers()) > 0 || len(role.getRoles()) > 0 {\n\t\t\tdomains = append(domains, domain)\n\t\t}\n\t\treturn true\n\t})\n\treturn domains, nil\n}\n\n// GetAllDomains gets all domains.\nfunc (dm *DomainManager) GetAllDomains() ([]string, error) {\n\tvar domains []string\n\tdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tdomains = append(domains, key.(string))\n\t\treturn true\n\t})\n\treturn domains, nil\n}\n\n// Deprecated: BuildRelationship is no longer required.\nfunc (dm *DomainManager) BuildRelationship(name1 string, name2 string, domain ...string) error {\n\treturn nil\n}\n\n// DeleteDomain deletes the specified domain from DomainManager.\nfunc (dm *DomainManager) DeleteDomain(domain string) error {\n\tdm.rmMap.Delete(domain)\n\treturn nil\n}\n\ntype RoleManager struct {\n\t*DomainManager\n}\n\nfunc NewRoleManager(maxHierarchyLevel int) *RoleManager {\n\trm := &RoleManager{}\n\trm.DomainManager = NewDomainManager(maxHierarchyLevel)\n\treturn rm\n}\n\n// DeleteDomain does nothing for RoleManagerImpl (no domain concept).\nfunc (rm *RoleManagerImpl) DeleteDomain(domain string) error {\n\treturn errors.New(\"DeleteDomain is not supported by RoleManagerImpl (no domain concept)\")\n}\n\ntype ConditionalRoleManager struct {\n\tRoleManagerImpl\n}\n\nfunc (crm *ConditionalRoleManager) copyFrom(other *ConditionalRoleManager) {\n\tother.Range(func(name1, name2 string, domain ...string) bool {\n\t\t_ = crm.AddLink(name1, name2, domain...)\n\t\treturn true\n\t})\n}\n\n// use this constructor to avoid rebuild of AddMatchingFunc.\nfunc newConditionalRoleManagerWithMatchingFunc(maxHierarchyLevel int, fn rbac.MatchingFunc) *ConditionalRoleManager {\n\trm := NewConditionalRoleManager(maxHierarchyLevel)\n\trm.matchingFunc = fn\n\treturn rm\n}\n\n// NewConditionalRoleManager is the constructor for creating an instance of the\n// ConditionalRoleManager implementation.\nfunc NewConditionalRoleManager(maxHierarchyLevel int) *ConditionalRoleManager {\n\trm := ConditionalRoleManager{}\n\t_ = rm.Clear() // init allRoles and matchingFuncCache\n\trm.maxHierarchyLevel = maxHierarchyLevel\n\treturn &rm\n}\n\n// HasLink determines whether role: name1 inherits role: name2.\nfunc (crm *ConditionalRoleManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {\n\tif name1 == name2 || (crm.matchingFunc != nil && crm.Match(name1, name2)) {\n\t\treturn true, nil\n\t}\n\n\t// Lock to prevent race conditions between getRole and removeRole\n\tcrm.mutex.Lock()\n\tdefer crm.mutex.Unlock()\n\n\tuser, userCreated := crm.getRole(name1)\n\trole, roleCreated := crm.getRole(name2)\n\n\tif userCreated {\n\t\tdefer crm.removeRole(user.name)\n\t}\n\tif roleCreated {\n\t\tdefer crm.removeRole(role.name)\n\t}\n\n\treturn crm.hasLinkHelper(role.name, map[string]*Role{user.name: user}, crm.maxHierarchyLevel, domains...), nil\n}\n\n// hasLinkHelper use the Breadth First Search algorithm to traverse the Role tree\n// Judging whether the user has a role (has link) is to judge whether the role node can be reached from the user node.\nfunc (crm *ConditionalRoleManager) hasLinkHelper(targetName string, roles map[string]*Role, level int, domains ...string) bool {\n\tif level < 0 || len(roles) == 0 {\n\t\treturn false\n\t}\n\tnextRoles := map[string]*Role{}\n\tfor _, role := range roles {\n\t\tif targetName == role.name || (crm.matchingFunc != nil && crm.Match(role.name, targetName)) {\n\t\t\treturn true\n\t\t}\n\t\trole.rangeRoles(func(key, value interface{}) bool {\n\t\t\tnextRole := value.(*Role)\n\t\t\treturn crm.getNextRoles(role, nextRole, domains, nextRoles)\n\t\t})\n\t}\n\n\treturn crm.hasLinkHelper(targetName, nextRoles, level-1)\n}\n\nfunc (crm *ConditionalRoleManager) getNextRoles(currentRole, nextRole *Role, domains []string, nextRoles map[string]*Role) bool {\n\tpassLinkConditionFunc, err := crm.checkLinkCondition(currentRole.name, nextRole.name, domains)\n\n\tif err != nil {\n\t\t// Logger has been removed - error is ignored\n\t\treturn false\n\t}\n\n\tif passLinkConditionFunc {\n\t\tnextRoles[nextRole.name] = nextRole\n\t}\n\n\treturn true\n}\n\nfunc (crm *ConditionalRoleManager) checkLinkCondition(name1, name2 string, domain []string) (bool, error) {\n\tpassLinkConditionFunc := true\n\tvar err error\n\n\tif len(domain) == 0 {\n\t\tif linkConditionFunc, existLinkCondition := crm.GetLinkConditionFunc(name1, name2); existLinkCondition {\n\t\t\tparams, _ := crm.GetLinkConditionFuncParams(name1, name2)\n\t\t\tpassLinkConditionFunc, err = linkConditionFunc(params...)\n\t\t}\n\t} else {\n\t\tif linkConditionFunc, existLinkCondition := crm.GetDomainLinkConditionFunc(name1, name2, domain[0]); existLinkCondition {\n\t\t\tparams, _ := crm.GetLinkConditionFuncParams(name1, name2, domain[0])\n\t\t\tpassLinkConditionFunc, err = linkConditionFunc(params...)\n\t\t}\n\t}\n\n\treturn passLinkConditionFunc, err\n}\n\nfunc (crm *ConditionalRoleManager) GetRoles(name string, domains ...string) ([]string, error) {\n\tuser, created := crm.getRole(name)\n\tif created {\n\t\tdefer crm.removeRole(user.name)\n\t}\n\tvar roles []string\n\tuser.rangeRoles(func(key, value interface{}) bool {\n\t\troleName := key.(string)\n\t\tpassLinkConditionFunc, err := crm.checkLinkCondition(name, roleName, domains)\n\t\tif err != nil {\n\t\t\t// Logger has been removed - error is ignored\n\t\t\treturn true\n\t\t}\n\n\t\tif passLinkConditionFunc {\n\t\t\troles = append(roles, roleName)\n\t\t}\n\n\t\treturn true\n\t})\n\treturn roles, nil\n}\n\nfunc (crm *ConditionalRoleManager) GetUsers(name string, domains ...string) ([]string, error) {\n\trole, created := crm.getRole(name)\n\tif created {\n\t\tdefer crm.removeRole(name)\n\t}\n\tvar users []string\n\trole.rangeUsers(func(key, value interface{}) bool {\n\t\tuserName := key.(string)\n\n\t\tpassLinkConditionFunc, err := crm.checkLinkCondition(userName, name, domains)\n\t\tif err != nil {\n\t\t\t// Logger has been removed - error is ignored\n\t\t\treturn true\n\t\t}\n\n\t\tif passLinkConditionFunc {\n\t\t\tusers = append(users, userName)\n\t\t}\n\n\t\treturn true\n\t})\n\n\treturn users, nil\n}\n\n// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel and link conditions.\nfunc (crm *ConditionalRoleManager) GetImplicitRoles(name string, domain ...string) ([]string, error) {\n\tuser, created := crm.getRole(name)\n\tif created {\n\t\tdefer crm.removeRole(user.name)\n\t}\n\n\tvar res []string\n\troleSet := make(map[string]bool)\n\troleSet[name] = true\n\troles := map[string]*Role{user.name: user}\n\n\treturn crm.getImplicitRolesHelper(roles, roleSet, res, 0, domain), nil\n}\n\n// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel and link conditions.\nfunc (crm *ConditionalRoleManager) GetImplicitUsers(name string, domain ...string) ([]string, error) {\n\trole, created := crm.getRole(name)\n\tif created {\n\t\tdefer crm.removeRole(role.name)\n\t}\n\n\tvar res []string\n\tuserSet := make(map[string]bool)\n\tuserSet[name] = true\n\tusers := map[string]*Role{role.name: role}\n\n\treturn crm.getImplicitUsersHelper(users, userSet, res, 0, domain), nil\n}\n\n// getImplicitRolesHelper is a helper function for GetImplicitRoles that respects maxHierarchyLevel and link conditions.\nfunc (crm *ConditionalRoleManager) getImplicitRolesHelper(roles map[string]*Role, roleSet map[string]bool, res []string, level int, domains []string) []string {\n\tif level >= crm.maxHierarchyLevel || len(roles) == 0 {\n\t\treturn res\n\t}\n\n\tnextRoles := map[string]*Role{}\n\tfor _, role := range roles {\n\t\trole.rangeRoles(func(key, value interface{}) bool {\n\t\t\troleName := key.(string)\n\t\t\tif _, ok := roleSet[roleName]; !ok {\n\t\t\t\tpassLinkConditionFunc, err := crm.checkLinkCondition(role.name, roleName, domains)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Logger has been removed - error is ignored\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tif passLinkConditionFunc {\n\t\t\t\t\tres = append(res, roleName)\n\t\t\t\t\troleSet[roleName] = true\n\t\t\t\t\tnextRoles[roleName] = value.(*Role)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\n\treturn crm.getImplicitRolesHelper(nextRoles, roleSet, res, level+1, domains)\n}\n\n// getImplicitUsersHelper is a helper function for GetImplicitUsers that respects maxHierarchyLevel and link conditions.\nfunc (crm *ConditionalRoleManager) getImplicitUsersHelper(users map[string]*Role, userSet map[string]bool, res []string, level int, domains []string) []string {\n\tif level >= crm.maxHierarchyLevel || len(users) == 0 {\n\t\treturn res\n\t}\n\n\tnextUsers := map[string]*Role{}\n\tfor _, user := range users {\n\t\tuser.rangeUsers(func(key, value interface{}) bool {\n\t\t\tuserName := key.(string)\n\t\t\tif _, ok := userSet[userName]; !ok {\n\t\t\t\tpassLinkConditionFunc, err := crm.checkLinkCondition(userName, user.name, domains)\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Logger has been removed - error is ignored\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tif passLinkConditionFunc {\n\t\t\t\t\tres = append(res, userName)\n\t\t\t\t\tuserSet[userName] = true\n\t\t\t\t\tnextUsers[userName] = value.(*Role)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\n\treturn crm.getImplicitUsersHelper(nextUsers, userSet, res, level+1, domains)\n}\n\n// GetLinkConditionFunc get LinkConditionFunc based on userName, roleName.\nfunc (crm *ConditionalRoleManager) GetLinkConditionFunc(userName, roleName string) (rbac.LinkConditionFunc, bool) {\n\treturn crm.GetDomainLinkConditionFunc(userName, roleName, defaultDomain)\n}\n\n// GetDomainLinkConditionFunc get LinkConditionFunc based on userName, roleName, domain.\nfunc (crm *ConditionalRoleManager) GetDomainLinkConditionFunc(userName, roleName, domain string) (rbac.LinkConditionFunc, bool) {\n\tuser, userCreated := crm.getRole(userName)\n\trole, roleCreated := crm.getRole(roleName)\n\n\tif userCreated {\n\t\tcrm.removeRole(user.name)\n\t\treturn nil, false\n\t}\n\n\tif roleCreated {\n\t\tcrm.removeRole(role.name)\n\t\treturn nil, false\n\t}\n\n\treturn user.getLinkConditionFunc(role, domain)\n}\n\n// GetLinkConditionFuncParams gets parameters of LinkConditionFunc based on userName, roleName, domain.\nfunc (crm *ConditionalRoleManager) GetLinkConditionFuncParams(userName, roleName string, domain ...string) ([]string, bool) {\n\tuser, userCreated := crm.getRole(userName)\n\trole, roleCreated := crm.getRole(roleName)\n\n\tif userCreated {\n\t\tcrm.removeRole(user.name)\n\t\treturn nil, false\n\t}\n\n\tif roleCreated {\n\t\tcrm.removeRole(role.name)\n\t\treturn nil, false\n\t}\n\n\tdomainName := defaultDomain\n\tif len(domain) != 0 {\n\t\tdomainName = domain[0]\n\t}\n\n\tif params, ok := user.getLinkConditionFuncParams(role, domainName); ok {\n\t\treturn params, true\n\t} else {\n\t\treturn nil, false\n\t}\n}\n\n// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.\nfunc (crm *ConditionalRoleManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {\n\tcrm.AddDomainLinkConditionFunc(userName, roleName, defaultDomain, fn)\n}\n\n// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.\nfunc (crm *ConditionalRoleManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {\n\tuser, _ := crm.getRole(userName)\n\trole, _ := crm.getRole(roleName)\n\n\tuser.addLinkConditionFunc(role, domain, fn)\n}\n\n// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.\nfunc (crm *ConditionalRoleManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {\n\tcrm.SetDomainLinkConditionFuncParams(userName, roleName, defaultDomain, params...)\n}\n\n// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.\nfunc (crm *ConditionalRoleManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {\n\tuser, _ := crm.getRole(userName)\n\trole, _ := crm.getRole(roleName)\n\n\tuser.setLinkConditionFuncParams(role, domain, params...)\n}\n\ntype ConditionalDomainManager struct {\n\tConditionalRoleManager\n\tDomainManager\n}\n\n// NewConditionalDomainManager is the constructor for creating an instance of the\n// ConditionalDomainManager implementation.\nfunc NewConditionalDomainManager(maxHierarchyLevel int) *ConditionalDomainManager {\n\trm := ConditionalDomainManager{}\n\t_ = rm.Clear() // init allRoles and matchingFuncCache\n\trm.maxHierarchyLevel = maxHierarchyLevel\n\treturn &rm\n}\n\nfunc (cdm *ConditionalDomainManager) load(name interface{}) (value *ConditionalRoleManager, ok bool) {\n\tif r, ok := cdm.rmMap.Load(name); ok {\n\t\treturn r.(*ConditionalRoleManager), true\n\t}\n\treturn nil, false\n}\n\n// load or create a ConditionalRoleManager instance of domain.\nfunc (cdm *ConditionalDomainManager) getConditionalRoleManager(domain string, store bool) *ConditionalRoleManager {\n\tvar rm *ConditionalRoleManager\n\tvar ok bool\n\n\tif rm, ok = cdm.load(domain); !ok {\n\t\trm = newConditionalRoleManagerWithMatchingFunc(cdm.maxHierarchyLevel, cdm.matchingFunc)\n\t\tif store {\n\t\t\tcdm.rmMap.Store(domain, rm)\n\t\t}\n\t\tif cdm.domainMatchingFunc != nil {\n\t\t\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\t\t\tdomain2 := key.(string)\n\t\t\t\trm2 := value.(*ConditionalRoleManager)\n\t\t\t\tif domain != domain2 && cdm.Match(domain, domain2) {\n\t\t\t\t\trm.copyFrom(rm2)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\treturn rm\n}\n\n// HasLink determines whether role: name1 inherits role: name2.\nfunc (cdm *ConditionalDomainManager) HasLink(name1 string, name2 string, domains ...string) (bool, error) {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\trm := cdm.getConditionalRoleManager(domain, false)\n\treturn rm.HasLink(name1, name2, domains...)\n}\n\nfunc (cdm *ConditionalDomainManager) GetRoles(name string, domains ...string) ([]string, error) {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcrm := cdm.getConditionalRoleManager(domain, false)\n\treturn crm.GetRoles(name, domains...)\n}\n\nfunc (cdm *ConditionalDomainManager) GetUsers(name string, domains ...string) ([]string, error) {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcrm := cdm.getConditionalRoleManager(domain, false)\n\treturn crm.GetUsers(name, domains...)\n}\n\nfunc (cdm *ConditionalDomainManager) GetImplicitRoles(name string, domains ...string) ([]string, error) {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcrm := cdm.getConditionalRoleManager(domain, false)\n\treturn crm.GetImplicitRoles(name, domains...)\n}\n\nfunc (cdm *ConditionalDomainManager) GetImplicitUsers(name string, domains ...string) ([]string, error) {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcrm := cdm.getConditionalRoleManager(domain, false)\n\treturn crm.GetImplicitUsers(name, domains...)\n}\n\n// AddLink adds the inheritance link between role: name1 and role: name2.\n// aka role: name1 inherits role: name2.\nfunc (cdm *ConditionalDomainManager) AddLink(name1 string, name2 string, domains ...string) error {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist\n\t_ = conditionalRoleManager.AddLink(name1, name2, domain)\n\n\tcdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {\n\t\t_ = rm.AddLink(name1, name2, domain)\n\t})\n\treturn nil\n}\n\n// DeleteLink deletes the inheritance link between role: name1 and role: name2.\n// aka role: name1 does not inherit role: name2 any more.\nfunc (cdm *ConditionalDomainManager) DeleteLink(name1 string, name2 string, domains ...string) error {\n\tdomain, err := cdm.getDomain(domains...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconditionalRoleManager := cdm.getConditionalRoleManager(domain, true) // create role manager if it does not exist\n\t_ = conditionalRoleManager.DeleteLink(name1, name2, domain)\n\n\tcdm.rangeAffectedRoleManagers(domain, func(rm *RoleManagerImpl) {\n\t\t_ = rm.DeleteLink(name1, name2, domain)\n\t})\n\treturn nil\n}\n\n// AddLinkConditionFunc is based on userName, roleName, add LinkConditionFunc.\nfunc (cdm *ConditionalDomainManager) AddLinkConditionFunc(userName, roleName string, fn rbac.LinkConditionFunc) {\n\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*ConditionalRoleManager).AddLinkConditionFunc(userName, roleName, fn)\n\t\treturn true\n\t})\n}\n\n// AddDomainLinkConditionFunc is based on userName, roleName, domain, add LinkConditionFunc.\nfunc (cdm *ConditionalDomainManager) AddDomainLinkConditionFunc(userName, roleName, domain string, fn rbac.LinkConditionFunc) {\n\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*ConditionalRoleManager).AddDomainLinkConditionFunc(userName, roleName, domain, fn)\n\t\treturn true\n\t})\n}\n\n// SetLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName.\nfunc (cdm *ConditionalDomainManager) SetLinkConditionFuncParams(userName, roleName string, params ...string) {\n\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*ConditionalRoleManager).SetLinkConditionFuncParams(userName, roleName, params...)\n\t\treturn true\n\t})\n}\n\n// SetDomainLinkConditionFuncParams sets parameters of LinkConditionFunc based on userName, roleName, domain.\nfunc (cdm *ConditionalDomainManager) SetDomainLinkConditionFuncParams(userName, roleName, domain string, params ...string) {\n\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*ConditionalRoleManager).SetDomainLinkConditionFuncParams(userName, roleName, domain, params...)\n\t\treturn true\n\t})\n}\n\n// AddDomainMatchingFunc support use domain pattern in g.\nfunc (cdm *ConditionalDomainManager) AddDomainMatchingFunc(name string, fn rbac.MatchingFunc) {\n\tcdm.domainMatchingFunc = fn\n\tcdm.rmMap.Range(func(key, value interface{}) bool {\n\t\tvalue.(*ConditionalRoleManager).AddDomainMatchingFunc(name, fn)\n\t\treturn true\n\t})\n\tcdm.rebuild()\n}\n\n// rebuild clears the map of ConditionalRoleManagers.\nfunc (cdm *ConditionalDomainManager) rebuild() {\n\trmMap := cdm.rmMap\n\t_ = cdm.Clear()\n\trmMap.Range(func(key, value interface{}) bool {\n\t\tdomain := key.(string)\n\t\tcrm := value.(*ConditionalRoleManager)\n\n\t\tcrm.Range(func(name1, name2 string, _ ...string) bool {\n\t\t\t_ = cdm.AddLink(name1, name2, domain)\n\t\t\treturn true\n\t\t})\n\t\treturn true\n\t})\n}\n"
  },
  {
    "path": "rbac/default-role-manager/role_manager_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage defaultrolemanager\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc testRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, res bool) {\n\tt.Helper()\n\tmyRes, _ := rm.HasLink(name1, name2)\n\tt.Logf(\"%s, %s: %t\", name1, name2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", name1, name2, !res, res)\n\t}\n}\n\nfunc testDomainRole(t *testing.T, rm rbac.RoleManager, name1 string, name2 string, domain string, res bool) {\n\tt.Helper()\n\tmyRes, _ := rm.HasLink(name1, name2, domain)\n\tt.Logf(\"%s :: %s, %s: %t\", domain, name1, name2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s :: %s < %s: %t, supposed to be %t\", domain, name1, name2, !res, res)\n\t}\n}\n\nfunc testPrintRoles(t *testing.T, rm rbac.RoleManager, name string, res []string) {\n\tt.Helper()\n\tmyRes, _ := rm.GetRoles(name)\n\tt.Logf(\"%s: %s\", name, myRes)\n\n\tif !util.SetEquals(myRes, res) {\n\t\tt.Errorf(\"%s: %s, supposed to be %s\", name, myRes, res)\n\t}\n}\n\nfunc testPrintUsers(t *testing.T, rm rbac.RoleManager, name string, res []string) {\n\tt.Helper()\n\tmyRes, _ := rm.GetUsers(name)\n\tt.Logf(\"%s: %s\", name, myRes)\n\n\tif !util.SetEquals(myRes, res) {\n\t\tt.Errorf(\"%s: %s, supposed to be %s\", name, myRes, res)\n\t}\n}\n\nfunc testPrintRolesWithDomain(t *testing.T, rm rbac.RoleManager, name string, domain string, res []string) {\n\tt.Helper()\n\tmyRes, _ := rm.GetRoles(name, domain)\n\n\tif !util.SetEquals(myRes, res) {\n\t\tt.Errorf(\"%s: %s, supposed to be %s\", name, myRes, res)\n\t}\n}\n\nfunc TestRole(t *testing.T) {\n\trm := NewRoleManager(3)\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\t_ = rm.AddLink(\"u2\", \"g1\")\n\t_ = rm.AddLink(\"u3\", \"g2\")\n\t_ = rm.AddLink(\"u4\", \"g2\")\n\t_ = rm.AddLink(\"u4\", \"g3\")\n\t_ = rm.AddLink(\"g1\", \"g3\")\n\n\t// Current role inheritance tree:\n\t//             g3    g2\n\t//            /  \\  /  \\\n\t//          g1    u4    u3\n\t//         /  \\\n\t//       u1    u2\n\n\ttestRole(t, rm, \"u1\", \"g1\", true)\n\ttestRole(t, rm, \"u1\", \"g2\", false)\n\ttestRole(t, rm, \"u1\", \"g3\", true)\n\ttestRole(t, rm, \"u2\", \"g1\", true)\n\ttestRole(t, rm, \"u2\", \"g2\", false)\n\ttestRole(t, rm, \"u2\", \"g3\", true)\n\ttestRole(t, rm, \"u3\", \"g1\", false)\n\ttestRole(t, rm, \"u3\", \"g2\", true)\n\ttestRole(t, rm, \"u3\", \"g3\", false)\n\ttestRole(t, rm, \"u4\", \"g1\", false)\n\ttestRole(t, rm, \"u4\", \"g2\", true)\n\ttestRole(t, rm, \"u4\", \"g3\", true)\n\n\ttestPrintRoles(t, rm, \"u1\", []string{\"g1\"})\n\ttestPrintRoles(t, rm, \"u2\", []string{\"g1\"})\n\ttestPrintRoles(t, rm, \"u3\", []string{\"g2\"})\n\ttestPrintRoles(t, rm, \"u4\", []string{\"g2\", \"g3\"})\n\ttestPrintRoles(t, rm, \"g1\", []string{\"g3\"})\n\ttestPrintRoles(t, rm, \"g2\", []string{})\n\ttestPrintRoles(t, rm, \"g3\", []string{})\n\n\t_ = rm.DeleteLink(\"g1\", \"g3\")\n\t_ = rm.DeleteLink(\"u4\", \"g2\")\n\n\t// Current role inheritance tree after deleting the links:\n\t//             g3    g2\n\t//               \\     \\\n\t//          g1    u4    u3\n\t//         /  \\\n\t//       u1    u2\n\n\ttestRole(t, rm, \"u1\", \"g1\", true)\n\ttestRole(t, rm, \"u1\", \"g2\", false)\n\ttestRole(t, rm, \"u1\", \"g3\", false)\n\ttestRole(t, rm, \"u2\", \"g1\", true)\n\ttestRole(t, rm, \"u2\", \"g2\", false)\n\ttestRole(t, rm, \"u2\", \"g3\", false)\n\ttestRole(t, rm, \"u3\", \"g1\", false)\n\ttestRole(t, rm, \"u3\", \"g2\", true)\n\ttestRole(t, rm, \"u3\", \"g3\", false)\n\ttestRole(t, rm, \"u4\", \"g1\", false)\n\ttestRole(t, rm, \"u4\", \"g2\", false)\n\ttestRole(t, rm, \"u4\", \"g3\", true)\n\n\ttestPrintRoles(t, rm, \"u1\", []string{\"g1\"})\n\ttestPrintRoles(t, rm, \"u2\", []string{\"g1\"})\n\ttestPrintRoles(t, rm, \"u3\", []string{\"g2\"})\n\ttestPrintRoles(t, rm, \"u4\", []string{\"g3\"})\n\ttestPrintRoles(t, rm, \"g1\", []string{})\n\ttestPrintRoles(t, rm, \"g2\", []string{})\n\ttestPrintRoles(t, rm, \"g3\", []string{})\n\n\trm = NewRoleManager(3)\n\trm.AddMatchingFunc(\"keyMatch\", util.KeyMatch)\n\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\t_ = rm.AddLink(\"u1\", \"*\")\n\t_ = rm.AddLink(\"u2\", \"g2\")\n\n\t// Current role inheritance tree after deleting the links:\n\t//          g1   g2\n\t//            \\ /  \\\n\t//             *    u2\n\t//             |\n\t//             u1\n\ttestRole(t, rm, \"u1\", \"g1\", true)\n\ttestRole(t, rm, \"u1\", \"g2\", true)\n\ttestRole(t, rm, \"u2\", \"g2\", true)\n\ttestRole(t, rm, \"u2\", \"g1\", false)\n\ttestPrintRoles(t, rm, \"u1\", []string{\"*\", \"u1\", \"u2\", \"g1\", \"g2\"})\n\ttestPrintUsers(t, rm, \"*\", []string{\"u1\"})\n}\n\nfunc TestDomainRole(t *testing.T) {\n\trm := NewRoleManager(3)\n\t_ = rm.AddLink(\"u1\", \"g1\", \"domain1\")\n\t_ = rm.AddLink(\"u2\", \"g1\", \"domain1\")\n\t_ = rm.AddLink(\"u3\", \"admin\", \"domain2\")\n\t_ = rm.AddLink(\"u4\", \"admin\", \"domain2\")\n\t_ = rm.AddLink(\"u4\", \"admin\", \"domain1\")\n\t_ = rm.AddLink(\"g1\", \"admin\", \"domain1\")\n\n\t// Current role inheritance tree:\n\t//       domain1:admin    domain2:admin\n\t//            /       \\  /       \\\n\t//      domain1:g1     u4         u3\n\t//         /  \\\n\t//       u1    u2\n\n\ttestDomainRole(t, rm, \"u1\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u1\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u1\", \"admin\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u1\", \"admin\", \"domain2\", false)\n\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u2\", \"admin\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u2\", \"admin\", \"domain2\", false)\n\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u3\", \"admin\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u3\", \"admin\", \"domain2\", true)\n\n\ttestDomainRole(t, rm, \"u4\", \"g1\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u4\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u4\", \"admin\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u4\", \"admin\", \"domain2\", true)\n\n\t_ = rm.DeleteLink(\"g1\", \"admin\", \"domain1\")\n\t_ = rm.DeleteLink(\"u4\", \"admin\", \"domain2\")\n\n\t// Current role inheritance tree after deleting the links:\n\t//       domain1:admin    domain2:admin\n\t//                    \\          \\\n\t//      domain1:g1     u4         u3\n\t//         /  \\\n\t//       u1    u2\n\n\ttestDomainRole(t, rm, \"u1\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u1\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u1\", \"admin\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u1\", \"admin\", \"domain2\", false)\n\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u2\", \"admin\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u2\", \"admin\", \"domain2\", false)\n\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u3\", \"admin\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u3\", \"admin\", \"domain2\", true)\n\n\ttestDomainRole(t, rm, \"u4\", \"g1\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u4\", \"g1\", \"domain2\", false)\n\ttestDomainRole(t, rm, \"u4\", \"admin\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u4\", \"admin\", \"domain2\", false)\n}\n\nfunc TestClear(t *testing.T) {\n\trm := NewRoleManager(3)\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\t_ = rm.AddLink(\"u2\", \"g1\")\n\t_ = rm.AddLink(\"u3\", \"g2\")\n\t_ = rm.AddLink(\"u4\", \"g2\")\n\t_ = rm.AddLink(\"u4\", \"g3\")\n\t_ = rm.AddLink(\"g1\", \"g3\")\n\n\t// Current role inheritance tree:\n\t//             g3    g2\n\t//            /  \\  /  \\\n\t//          g1    u4    u3\n\t//         /  \\\n\t//       u1    u2\n\n\t_ = rm.Clear()\n\n\t// All data is cleared.\n\t// No role inheritance now.\n\n\ttestRole(t, rm, \"u1\", \"g1\", false)\n\ttestRole(t, rm, \"u1\", \"g2\", false)\n\ttestRole(t, rm, \"u1\", \"g3\", false)\n\ttestRole(t, rm, \"u2\", \"g1\", false)\n\ttestRole(t, rm, \"u2\", \"g2\", false)\n\ttestRole(t, rm, \"u2\", \"g3\", false)\n\ttestRole(t, rm, \"u3\", \"g1\", false)\n\ttestRole(t, rm, \"u3\", \"g2\", false)\n\ttestRole(t, rm, \"u3\", \"g3\", false)\n\ttestRole(t, rm, \"u4\", \"g1\", false)\n\ttestRole(t, rm, \"u4\", \"g2\", false)\n\ttestRole(t, rm, \"u4\", \"g3\", false)\n}\n\nfunc TestDomainPatternRole(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddDomainMatchingFunc(\"keyMatch2\", util.KeyMatch2)\n\n\t_ = rm.AddLink(\"u1\", \"g1\", \"domain1\")\n\t_ = rm.AddLink(\"u2\", \"g1\", \"domain2\")\n\t_ = rm.AddLink(\"u3\", \"g1\", \"*\")\n\t_ = rm.AddLink(\"u4\", \"g2\", \"domain3\")\n\t// Current role inheritance tree after deleting the links:\n\t//       domain1:g1    domain2:g1\t\t\tdomain3:g2\n\t//\t\t   /      \\    /      \\\t\t\t\t\t|\n\t//\t domain1:u1    *:g1     domain2:u2\t\tdomain3:u4\n\t// \t\t\t\t\t|\n\t// \t\t\t\t   *:u3\n\ttestDomainRole(t, rm, \"u1\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u2\", \"g1\", \"domain2\", true)\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"u3\", \"g1\", \"domain2\", true)\n\ttestDomainRole(t, rm, \"u1\", \"g2\", \"domain1\", false)\n\ttestDomainRole(t, rm, \"u4\", \"g2\", \"domain3\", true)\n\ttestDomainRole(t, rm, \"u3\", \"g2\", \"domain3\", false)\n\n\ttestPrintRolesWithDomain(t, rm, \"u3\", \"domain1\", []string{\"g1\"})\n\ttestPrintRolesWithDomain(t, rm, \"u1\", \"domain1\", []string{\"g1\"})\n\ttestPrintRolesWithDomain(t, rm, \"u3\", \"domain2\", []string{\"g1\"})\n\ttestPrintRolesWithDomain(t, rm, \"u1\", \"domain2\", []string{})\n\ttestPrintRolesWithDomain(t, rm, \"u4\", \"domain3\", []string{\"g2\"})\n}\n\nfunc TestAllMatchingFunc(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddMatchingFunc(\"keyMatch2\", util.KeyMatch2)\n\trm.AddDomainMatchingFunc(\"keyMatch2\", util.KeyMatch2)\n\n\t_ = rm.AddLink(\"/book/:id\", \"book_group\", \"*\")\n\t// Current role inheritance tree after deleting the links:\n\t//  \t\t*:book_group\n\t//\t\t\t\t|\n\t// \t\t\t*:/book/:id\n\ttestDomainRole(t, rm, \"/book/1\", \"book_group\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"/book/2\", \"book_group\", \"domain1\", true)\n}\n\nfunc TestMatchingFuncOrder(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddMatchingFunc(\"regexMatch\", util.RegexMatch)\n\n\t_ = rm.AddLink(\"g\\\\d+\", \"root\")\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\ttestRole(t, rm, \"u1\", \"root\", true)\n\n\t_ = rm.Clear()\n\n\t_ = rm.AddLink(\"u1\", \"g1\")\n\t_ = rm.AddLink(\"g\\\\d+\", \"root\")\n\ttestRole(t, rm, \"u1\", \"root\", true)\n\n\t_ = rm.Clear()\n\n\t_ = rm.AddLink(\"u1\", \"g\\\\d+\")\n\ttestRole(t, rm, \"u1\", \"g1\", true)\n\ttestRole(t, rm, \"u1\", \"g2\", true)\n}\n\nfunc TestDomainMatchingFuncWithDifferentDomain(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddDomainMatchingFunc(\"keyMatch\", util.KeyMatch)\n\n\t_ = rm.AddLink(\"alice\", \"editor\", \"*\")\n\t_ = rm.AddLink(\"editor\", \"admin\", \"domain1\")\n\n\ttestDomainRole(t, rm, \"alice\", \"admin\", \"domain1\", true)\n\ttestDomainRole(t, rm, \"alice\", \"admin\", \"domain2\", false)\n}\n\nfunc TestTemporaryRoles(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddMatchingFunc(\"regexMatch\", util.RegexMatch)\n\n\t_ = rm.AddLink(\"u\\\\d+\", \"user\")\n\n\tfor i := 0; i < 10; i++ {\n\t\ttestRole(t, rm, fmt.Sprintf(\"u%d\", i), \"user\", true)\n\t}\n\n\ttestPrintUsers(t, rm, \"user\", []string{\"u\\\\d+\"})\n\ttestPrintRoles(t, rm, \"u1\", []string{\"user\"})\n\n\t_ = rm.AddLink(\"u1\", \"manager\")\n\n\tfor i := 10; i < 20; i++ {\n\t\ttestRole(t, rm, fmt.Sprintf(\"u%d\", i), \"user\", true)\n\t}\n\n\ttestPrintUsers(t, rm, \"user\", []string{\"u\\\\d+\", \"u1\"})\n\ttestPrintRoles(t, rm, \"u1\", []string{\"user\", \"manager\"})\n}\n\nfunc TestMaxHierarchyLevel(t *testing.T) {\n\trm := NewRoleManager(1)\n\t_ = rm.AddLink(\"level0\", \"level1\")\n\t_ = rm.AddLink(\"level1\", \"level2\")\n\t_ = rm.AddLink(\"level2\", \"level3\")\n\n\ttestRole(t, rm, \"level0\", \"level0\", true)\n\ttestRole(t, rm, \"level0\", \"level1\", true)\n\ttestRole(t, rm, \"level0\", \"level2\", false)\n\ttestRole(t, rm, \"level0\", \"level3\", false)\n\ttestRole(t, rm, \"level1\", \"level2\", true)\n\ttestRole(t, rm, \"level1\", \"level3\", false)\n\n\trm = NewRoleManager(2)\n\t_ = rm.AddLink(\"level0\", \"level1\")\n\t_ = rm.AddLink(\"level1\", \"level2\")\n\t_ = rm.AddLink(\"level2\", \"level3\")\n\n\ttestRole(t, rm, \"level0\", \"level0\", true)\n\ttestRole(t, rm, \"level0\", \"level1\", true)\n\ttestRole(t, rm, \"level0\", \"level2\", true)\n\ttestRole(t, rm, \"level0\", \"level3\", false)\n\ttestRole(t, rm, \"level1\", \"level2\", true)\n\ttestRole(t, rm, \"level1\", \"level3\", true)\n}\n\n// TestConcurrentHasLink tests concurrent HasLink calls for race conditions.\n// This test verifies that concurrent HasLink calls with matching functions\n// don't produce inconsistent results due to temporary role creation/deletion races.\n// Regression test for issue #1318.\nfunc TestConcurrentHasLink(t *testing.T) {\n\trm := NewRoleManager(10)\n\trm.AddMatchingFunc(\"keyMatch2\", util.KeyMatch2)\n\n\t_ = rm.AddLink(\"alice\", \"admin\")\n\t_ = rm.AddLink(\"admin\", \"/data/*\")\n\n\texpected, _ := rm.HasLink(\"alice\", \"/data/123\")\n\n\tconst numGoroutines = 20\n\tconst numIterations = 100\n\n\tvar inconsistencies int64\n\tvar wg sync.WaitGroup\n\n\tfor i := 0; i < numGoroutines; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor j := 0; j < numIterations; j++ {\n\t\t\t\tresult, err := rm.HasLink(\"alice\", \"/data/123\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"HasLink failed: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t} else if result != expected {\n\t\t\t\t\tatomic.AddInt64(&inconsistencies, 1)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\tif inconsistencies > 0 {\n\t\tt.Errorf(\"Found %d inconsistencies in %d total operations\",\n\t\t\tinconsistencies, numGoroutines*numIterations)\n\t}\n}\n"
  },
  {
    "path": "rbac/role_manager.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage rbac\n\ntype MatchingFunc func(arg1 string, arg2 string) bool\n\ntype LinkConditionFunc = func(args ...string) (bool, error)\n\n// RoleManager provides interface to define the operations for managing roles.\ntype RoleManager interface {\n\t// Clear clears all stored data and resets the role manager to the initial state.\n\tClear() error\n\t// AddLink adds the inheritance link between two roles. role: name1 and role: name2.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tAddLink(name1 string, name2 string, domain ...string) error\n\t// Deprecated: BuildRelationship is no longer required\n\tBuildRelationship(name1 string, name2 string, domain ...string) error\n\t// DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tDeleteLink(name1 string, name2 string, domain ...string) error\n\t// HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tHasLink(name1 string, name2 string, domain ...string) (bool, error)\n\t// GetRoles gets the roles that a user inherits.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tGetRoles(name string, domain ...string) ([]string, error)\n\t// GetUsers gets the users that inherits a role.\n\t// domain is a prefix to the users (can be used for other purposes).\n\tGetUsers(name string, domain ...string) ([]string, error)\n\t// GetImplicitRoles gets the implicit roles that a user inherits, respecting maxHierarchyLevel.\n\t// domain is a prefix to the roles (can be used for other purposes).\n\tGetImplicitRoles(name string, domain ...string) ([]string, error)\n\t// GetImplicitUsers gets the implicit users that inherits a role, respecting maxHierarchyLevel.\n\t// domain is a prefix to the users (can be used for other purposes).\n\tGetImplicitUsers(name string, domain ...string) ([]string, error)\n\t// GetDomains gets domains that a user has\n\tGetDomains(name string) ([]string, error)\n\t// GetAllDomains gets all domains\n\tGetAllDomains() ([]string, error)\n\t// PrintRoles prints all the roles to log.\n\tPrintRoles() error\n\t// Match matches the domain with the pattern\n\tMatch(str string, pattern string) bool\n\t// AddMatchingFunc adds the matching function\n\tAddMatchingFunc(name string, fn MatchingFunc)\n\t// AddDomainMatchingFunc adds the domain matching function\n\tAddDomainMatchingFunc(name string, fn MatchingFunc)\n\t// DeleteDomain deletes all data of a domain in the role manager.\n\tDeleteDomain(domain string) error\n}\n\n// ConditionalRoleManager provides interface to define the operations for managing roles.\n// Link with conditions is supported.\ntype ConditionalRoleManager interface {\n\tRoleManager\n\n\t// AddLinkConditionFunc Add condition function fn for Link userName->roleName,\n\t// when fn returns true, Link is valid, otherwise invalid\n\tAddLinkConditionFunc(userName, roleName string, fn LinkConditionFunc)\n\t// SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName\n\tSetLinkConditionFuncParams(userName, roleName string, params ...string)\n\t// AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},\n\t// when fn returns true, Link is valid, otherwise invalid\n\tAddDomainLinkConditionFunc(user string, role string, domain string, fn LinkConditionFunc)\n\t// SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn\n\t// for Link userName->{roleName, domain}\n\tSetDomainLinkConditionFuncParams(user string, role string, domain string, params ...string)\n}\n"
  },
  {
    "path": "rbac_api.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// GetRolesForUser gets the roles that a user has.\nfunc (e *Enforcer) GetRolesForUser(name string, domain ...string) ([]string, error) {\n\trm := e.GetRoleManager()\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\tres, err := rm.GetRoles(name, domain...)\n\treturn res, err\n}\n\n// GetUsersForRole gets the users that has a role.\nfunc (e *Enforcer) GetUsersForRole(name string, domain ...string) ([]string, error) {\n\trm := e.GetRoleManager()\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\tres, err := rm.GetUsers(name, domain...)\n\treturn res, err\n}\n\n// HasRoleForUser determines whether a user has a role.\nfunc (e *Enforcer) HasRoleForUser(name string, role string, domain ...string) (bool, error) {\n\troles, err := e.GetRolesForUser(name, domain...)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\thasRole := false\n\tfor _, r := range roles {\n\t\tif r == role {\n\t\t\thasRole = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn hasRole, nil\n}\n\n// AddRoleForUser adds a role for a user.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *Enforcer) AddRoleForUser(user string, role string, domain ...string) (bool, error) {\n\targs := []string{user, role}\n\targs = append(args, domain...)\n\treturn e.AddGroupingPolicy(args)\n}\n\n// AddRolesForUser adds roles for a user.\n// Returns false if the user already has the roles (aka not affected).\nfunc (e *Enforcer) AddRolesForUser(user string, roles []string, domain ...string) (bool, error) {\n\tvar rules [][]string\n\tfor _, role := range roles {\n\t\trule := []string{user, role}\n\t\trule = append(rule, domain...)\n\t\trules = append(rules, rule)\n\t}\n\treturn e.AddGroupingPolicies(rules)\n}\n\n// DeleteRoleForUser deletes a role for a user.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *Enforcer) DeleteRoleForUser(user string, role string, domain ...string) (bool, error) {\n\targs := []string{user, role}\n\targs = append(args, domain...)\n\treturn e.RemoveGroupingPolicy(args)\n}\n\n// DeleteRolesForUser deletes all roles for a user.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *Enforcer) DeleteRolesForUser(user string, domain ...string) (bool, error) {\n\tvar args []string\n\tif len(domain) == 0 {\n\t\targs = []string{user}\n\t} else if len(domain) > 1 {\n\t\treturn false, errors.ErrDomainParameter\n\t} else {\n\t\targs = []string{user, \"\", domain[0]}\n\t}\n\treturn e.RemoveFilteredGroupingPolicy(0, args...)\n}\n\n// DeleteUser deletes a user.\n// Returns false if the user does not exist (aka not affected).\nfunc (e *Enforcer) DeleteUser(user string) (bool, error) {\n\tvar err error\n\tres1, err := e.RemoveFilteredGroupingPolicy(0, user)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres2, err := e.RemoveFilteredPolicy(subIndex, user)\n\treturn res1 || res2, err\n}\n\n// DeleteRole deletes a role.\n// Returns false if the role does not exist (aka not affected).\nfunc (e *Enforcer) DeleteRole(role string) (bool, error) {\n\tvar err error\n\tres1, err := e.RemoveFilteredGroupingPolicy(0, role)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tres2, err := e.RemoveFilteredGroupingPolicy(1, role)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres3, err := e.RemoveFilteredPolicy(subIndex, role)\n\treturn res1 || res2 || res3, err\n}\n\n// DeletePermission deletes a permission.\n// Returns false if the permission does not exist (aka not affected).\nfunc (e *Enforcer) DeletePermission(permission ...string) (bool, error) {\n\treturn e.RemoveFilteredPolicy(1, permission...)\n}\n\n// AddPermissionForUser adds a permission for a user or role.\n// Returns false if the user or role already has the permission (aka not affected).\nfunc (e *Enforcer) AddPermissionForUser(user string, permission ...string) (bool, error) {\n\treturn e.AddPolicy(util.JoinSlice(user, permission...))\n}\n\n// AddPermissionsForUser adds multiple permissions for a user or role.\n// Returns false if the user or role already has one of the permissions (aka not affected).\nfunc (e *Enforcer) AddPermissionsForUser(user string, permissions ...[]string) (bool, error) {\n\tvar rules [][]string\n\tfor _, permission := range permissions {\n\t\trules = append(rules, util.JoinSlice(user, permission...))\n\t}\n\treturn e.AddPolicies(rules)\n}\n\n// DeletePermissionForUser deletes a permission for a user or role.\n// Returns false if the user or role does not have the permission (aka not affected).\nfunc (e *Enforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) {\n\treturn e.RemovePolicy(util.JoinSlice(user, permission...))\n}\n\n// DeletePermissionsForUser deletes permissions for a user or role.\n// Returns false if the user or role does not have any permissions (aka not affected).\nfunc (e *Enforcer) DeletePermissionsForUser(user string) (bool, error) {\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn e.RemoveFilteredPolicy(subIndex, user)\n}\n\n// GetPermissionsForUser gets permissions for a user or role.\nfunc (e *Enforcer) GetPermissionsForUser(user string, domain ...string) ([][]string, error) {\n\treturn e.GetNamedPermissionsForUser(\"p\", user, domain...)\n}\n\n// GetNamedPermissionsForUser gets permissions for a user or role by named policy.\nfunc (e *Enforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {\n\tpermission := make([][]string, 0)\n\tfor pType, assertion := range e.model[\"p\"] {\n\t\tif pType != ptype {\n\t\t\tcontinue\n\t\t}\n\t\targs := make([]string, len(assertion.Tokens))\n\t\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\t\tif err != nil {\n\t\t\tsubIndex = 0\n\t\t}\n\t\targs[subIndex] = user\n\n\t\tif len(domain) > 0 {\n\t\t\tvar index int\n\t\t\tindex, err = e.GetFieldIndex(ptype, constant.DomainIndex)\n\t\t\tif err != nil {\n\t\t\t\treturn permission, err\n\t\t\t}\n\t\t\targs[index] = domain[0]\n\t\t}\n\t\tperm, err := e.GetFilteredNamedPolicy(ptype, 0, args...)\n\t\tif err != nil {\n\t\t\treturn permission, err\n\t\t}\n\t\tpermission = append(permission, perm...)\n\t}\n\treturn permission, nil\n}\n\n// HasPermissionForUser determines whether a user has a permission.\nfunc (e *Enforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {\n\treturn e.HasPolicy(util.JoinSlice(user, permission...))\n}\n\n// GetImplicitRolesForUser gets implicit roles that a user has.\n// Compared to GetRolesForUser(), this function retrieves indirect roles besides direct roles.\n// For example:\n// g, alice, role:admin\n// g, role:admin, role:user\n//\n// GetRolesForUser(\"alice\") can only get: [\"role:admin\"].\n// But GetImplicitRolesForUser(\"alice\") will get: [\"role:admin\", \"role:user\"].\nfunc (e *Enforcer) GetImplicitRolesForUser(name string, domain ...string) ([]string, error) {\n\tvar res []string\n\n\tfor rm := range e.rmMap {\n\t\troles, err := e.GetNamedImplicitRolesForUser(rm, name, domain...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, roles...)\n\t}\n\n\tfor crm := range e.condRmMap {\n\t\troles, err := e.GetNamedImplicitRolesForUser(crm, name, domain...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, roles...)\n\t}\n\n\treturn res, nil\n}\n\n// GetNamedImplicitRolesForUser gets implicit roles that a user has by named role definition.\n// Compared to GetImplicitRolesForUser(), this function retrieves indirect roles besides direct roles.\n// For example:\n// g, alice, role:admin\n// g, role:admin, role:user\n// g2, alice, role:admin2\n//\n// GetImplicitRolesForUser(\"alice\") can only get: [\"role:admin\", \"role:user\"].\n// But GetNamedImplicitRolesForUser(\"g2\", \"alice\") will get: [\"role:admin2\"].\nfunc (e *Enforcer) GetNamedImplicitRolesForUser(ptype string, name string, domain ...string) ([]string, error) {\n\trm := e.GetNamedRoleManager(ptype)\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager %s is not initialized\", ptype)\n\t}\n\n\t// Use the role manager's GetImplicitRoles method which respects maxHierarchyLevel\n\treturn rm.GetImplicitRoles(name, domain...)\n}\n\n// GetImplicitUsersForRole gets implicit users for a role.\nfunc (e *Enforcer) GetImplicitUsersForRole(name string, domain ...string) ([]string, error) {\n\tres := []string{}\n\tvar rms []rbac.RoleManager\n\n\tfor _, rm := range e.rmMap {\n\t\trms = append(rms, rm)\n\t}\n\tfor _, crm := range e.condRmMap {\n\t\trms = append(rms, crm)\n\t}\n\n\tfor _, rm := range rms {\n\t\t// Use the role manager's GetImplicitUsers method which respects maxHierarchyLevel\n\t\tusers, err := rm.GetImplicitUsers(name, domain...)\n\t\tif err != nil && err.Error() != \"error: name does not exist\" {\n\t\t\treturn nil, err\n\t\t}\n\t\tres = append(res, users...)\n\t}\n\n\treturn res, nil\n}\n\n// GetImplicitPermissionsForUser gets implicit permissions for a user or role.\n// Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles.\n// For example:\n// p, admin, data1, read\n// p, alice, data2, read\n// g, alice, admin\n//\n// GetPermissionsForUser(\"alice\") can only get: [[\"alice\", \"data2\", \"read\"]].\n// But GetImplicitPermissionsForUser(\"alice\") will get: [[\"admin\", \"data1\", \"read\"], [\"alice\", \"data2\", \"read\"]].\nfunc (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) {\n\treturn e.GetNamedImplicitPermissionsForUser(\"p\", \"g\", user, domain...)\n}\n\n// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.\n// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.\n// For example:\n// p, admin, data1, read\n// p2, admin, create\n// g, alice, admin\n//\n// GetImplicitPermissionsForUser(\"alice\") can only get: [[\"admin\", \"data1\", \"read\"]], whose policy is default policy \"p\"\n// But you can specify the named policy \"p2\" to get: [[\"admin\", \"create\"]] by    GetNamedImplicitPermissionsForUser(\"p2\",\"alice\").\nfunc (e *Enforcer) GetNamedImplicitPermissionsForUser(ptype string, gtype string, user string, domain ...string) ([][]string, error) {\n\tpermission := make([][]string, 0)\n\trm := e.GetNamedRoleManager(gtype)\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager %s is not initialized\", gtype)\n\t}\n\n\troles, err := e.GetNamedImplicitRolesForUser(gtype, user, domain...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpolicyRoles := make(map[string]struct{}, len(roles)+1)\n\tpolicyRoles[user] = struct{}{}\n\tfor _, r := range roles {\n\t\tpolicyRoles[r] = struct{}{}\n\t}\n\n\tdomainIndex, err := e.GetFieldIndex(ptype, constant.DomainIndex)\n\tfor _, rule := range e.model[\"p\"][ptype].Policy {\n\t\tif len(domain) == 0 {\n\t\t\tif _, ok := policyRoles[rule[0]]; ok {\n\t\t\t\tpermission = append(permission, deepCopyPolicy(rule))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif len(domain) > 1 {\n\t\t\treturn nil, errors.ErrDomainParameter\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\td := domain[0]\n\t\tmatched := rm.Match(d, rule[domainIndex])\n\t\tif !matched {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := policyRoles[rule[0]]; ok {\n\t\t\tnewRule := deepCopyPolicy(rule)\n\t\t\tnewRule[domainIndex] = d\n\t\t\tpermission = append(permission, newRule)\n\t\t}\n\t}\n\treturn permission, nil\n}\n\n// GetImplicitUsersForPermission gets implicit users for a permission.\n// For example:\n// p, admin, data1, read\n// p, bob, data1, read\n// g, alice, admin\n//\n// GetImplicitUsersForPermission(\"data1\", \"read\") will get: [\"alice\", \"bob\"].\n// Note: only users will be returned, roles (2nd arg in \"g\") will be excluded.\nfunc (e *Enforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) {\n\tpSubjects, err := e.GetAllSubjects()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgInherit, err := e.model.GetValuesForFieldInPolicyAllTypes(\"g\", 1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgSubjects, err := e.model.GetValuesForFieldInPolicyAllTypes(\"g\", 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsubjects := append(pSubjects, gSubjects...)\n\tutil.ArrayRemoveDuplicates(&subjects)\n\n\tsubjects = util.SetSubtract(subjects, gInherit)\n\n\tres := []string{}\n\tfor _, user := range subjects {\n\t\treq := util.JoinSliceAny(user, permission...)\n\t\tallowed, err := e.Enforce(req...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif allowed {\n\t\t\tres = append(res, user)\n\t\t}\n\t}\n\n\treturn res, nil\n}\n\n// GetDomainsForUser gets all domains.\nfunc (e *Enforcer) GetDomainsForUser(user string) ([]string, error) {\n\tvar domains []string\n\tfor _, rm := range e.rmMap {\n\t\tdomain, err := rm.GetDomains(user)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdomains = append(domains, domain...)\n\t}\n\tfor _, crm := range e.condRmMap {\n\t\tdomain, err := crm.GetDomains(user)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdomains = append(domains, domain...)\n\t}\n\treturn domains, nil\n}\n\n// GetImplicitResourcesForUser returns all policies that user obtaining in domain.\nfunc (e *Enforcer) GetImplicitResourcesForUser(user string, domain ...string) ([][]string, error) {\n\tpermissions, err := e.GetImplicitPermissionsForUser(user, domain...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres := make([][]string, 0)\n\tfor _, permission := range permissions {\n\t\tif permission[0] == user {\n\t\t\tres = append(res, permission)\n\t\t\tcontinue\n\t\t}\n\t\tresLocal := [][]string{{user}}\n\t\ttokensLength := len(permission)\n\t\tt := make([][]string, 1, tokensLength)\n\t\tfor _, token := range permission[1:] {\n\t\t\ttokens, err := e.GetImplicitUsersForRole(token, domain...)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttokens = append(tokens, token)\n\t\t\tt = append(t, tokens)\n\t\t}\n\t\tfor i := 1; i < tokensLength; i++ {\n\t\t\tn := make([][]string, 0)\n\t\t\tfor _, tokens := range t[i] {\n\t\t\t\tfor _, policy := range resLocal {\n\t\t\t\t\tt := append([]string(nil), policy...)\n\t\t\t\t\tt = append(t, tokens)\n\t\t\t\t\tn = append(n, t)\n\t\t\t\t}\n\t\t\t}\n\t\t\tresLocal = n\n\t\t}\n\t\tres = append(res, resLocal...)\n\t}\n\treturn res, nil\n}\n\n// deepCopyPolicy returns a deepcopy version of the policy to prevent changing policies through returned slice.\nfunc deepCopyPolicy(src []string) []string {\n\tnewRule := make([]string, len(src))\n\tcopy(newRule, src)\n\treturn newRule\n}\n\n// GetAllowedObjectConditions returns a string array of object conditions that the user can access.\n// For example: conditions, err := e.GetAllowedObjectConditions(\"alice\", \"read\", \"r.obj.\")\n// Note:\n//\n// 0. prefix: You can customize the prefix of the object conditions, and \"r.obj.\" is commonly used as a prefix.\n// After removing the prefix, the remaining part is the condition of the object.\n// If there is an obj policy that does not meet the prefix requirement, an errors.ERR_OBJ_CONDITION will be returned.\n//\n// 1. If the 'objectConditions' array is empty, return errors.ERR_EMPTY_CONDITION\n// This error is returned because some data adapters' ORM return full table data by default\n// when they receive an empty condition, which tends to behave contrary to expectations.(e.g. GORM)\n// If you are using an adapter that does not behave like this, you can choose to ignore this error.\nfunc (e *Enforcer) GetAllowedObjectConditions(user string, action string, prefix string) ([]string, error) {\n\tpermissions, err := e.GetImplicitPermissionsForUser(user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar objectConditions []string\n\tfor _, policy := range permissions {\n\t\t// policy {sub, obj, act}\n\t\tif policy[2] == action {\n\t\t\tif !strings.HasPrefix(policy[1], prefix) {\n\t\t\t\treturn nil, errors.ErrObjCondition\n\t\t\t}\n\t\t\tobjectConditions = append(objectConditions, strings.TrimPrefix(policy[1], prefix))\n\t\t}\n\t}\n\n\tif len(objectConditions) == 0 {\n\t\treturn nil, errors.ErrEmptyCondition\n\t}\n\n\treturn objectConditions, nil\n}\n\n// removeDuplicatePermissions Convert permissions to string as a hash to deduplicate.\nfunc removeDuplicatePermissions(permissions [][]string) [][]string {\n\tpermissionsSet := make(map[string]bool)\n\tres := make([][]string, 0)\n\tfor _, permission := range permissions {\n\t\tpermissionStr := util.ArrayToString(permission)\n\t\tif permissionsSet[permissionStr] {\n\t\t\tcontinue\n\t\t}\n\t\tpermissionsSet[permissionStr] = true\n\t\tres = append(res, permission)\n\t}\n\treturn res\n}\n\n// GetImplicitUsersForResource return implicit user based on resource.\n// for example:\n// p, alice, data1, read\n// p, bob, data2, write\n// p, data2_admin, data2, read\n// p, data2_admin, data2, write\n// g, alice, data2_admin\n// GetImplicitUsersForResource(\"data2\") will return [[bob data2 write] [alice data2 read] [alice data2 write]]\n// GetImplicitUsersForResource(\"data1\") will return [[alice data1 read]]\n// Note: only users will be returned, roles (2nd arg in \"g\") will be excluded.\nfunc (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, error) {\n\treturn e.GetNamedImplicitUsersForResource(\"g\", resource)\n}\n\n// GetNamedImplicitUsersForResource return implicit user based on resource with named policy support.\n// This function handles resource role relationships through named policies (e.g., g2, g3, etc.).\n// for example:\n// p, admin_group, admin_data, *\n// g, admin, admin_group\n// g2, app, admin_data\n// GetNamedImplicitUsersForResource(\"g2\", \"app\") will return users who have access to admin_data through g2 relationship.\nfunc (e *Enforcer) GetNamedImplicitUsersForResource(ptype string, resource string) ([][]string, error) {\n\tpermissions := make([][]string, 0)\n\tsubjectIndex, _ := e.GetFieldIndex(\"p\", \"sub\")\n\tobjectIndex, _ := e.GetFieldIndex(\"p\", \"obj\")\n\trm := e.GetRoleManager()\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\n\tisRole := make(map[string]bool)\n\troles, err := e.GetAllRoles()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, role := range roles {\n\t\tisRole[role] = true\n\t}\n\n\t// Get all resource types that the resource can access through ptype (e.g., g2)\n\tptypePolicies, _ := e.GetNamedGroupingPolicy(ptype)\n\tresourceAccessibleResourceTypes := make(map[string]bool)\n\n\tfor _, ptypePolicy := range ptypePolicies {\n\t\tif ptypePolicy[0] == resource { // ptypePolicy[0] is the resource\n\t\t\tresourceAccessibleResourceTypes[ptypePolicy[1]] = true // ptypePolicy[1] is the resource type it can access\n\t\t}\n\t}\n\n\tfor _, rule := range e.model[\"p\"][\"p\"].Policy {\n\t\tobj := rule[objectIndex]\n\t\tsub := rule[subjectIndex]\n\n\t\t// Check if this policy is directly for the resource OR for a resource type the resource can access\n\t\tif obj == resource || resourceAccessibleResourceTypes[obj] {\n\t\t\tif !isRole[sub] {\n\t\t\t\tpermissions = append(permissions, rule)\n\t\t\t} else {\n\t\t\t\tusers, err := rm.GetUsers(sub)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, user := range users {\n\t\t\t\t\timplicitUserRule := deepCopyPolicy(rule)\n\t\t\t\t\timplicitUserRule[subjectIndex] = user\n\t\t\t\t\tpermissions = append(permissions, implicitUserRule)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tres := removeDuplicatePermissions(permissions)\n\treturn res, nil\n}\n\n// GetImplicitUsersForResourceByDomain return implicit user based on resource and domain.\n// Compared to GetImplicitUsersForResource, domain is supported.\nfunc (e *Enforcer) GetImplicitUsersForResourceByDomain(resource string, domain string) ([][]string, error) {\n\tpermissions := make([][]string, 0)\n\tsubjectIndex, _ := e.GetFieldIndex(\"p\", \"sub\")\n\tobjectIndex, _ := e.GetFieldIndex(\"p\", \"obj\")\n\tdomIndex, _ := e.GetFieldIndex(\"p\", \"dom\")\n\trm := e.GetRoleManager()\n\tif rm == nil {\n\t\treturn nil, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\n\tisRole := make(map[string]bool)\n\n\tif roles, err := e.GetAllRolesByDomain(domain); err != nil {\n\t\treturn nil, err\n\t} else {\n\t\tfor _, role := range roles {\n\t\t\tisRole[role] = true\n\t\t}\n\t}\n\n\tfor _, rule := range e.model[\"p\"][\"p\"].Policy {\n\t\tobj := rule[objectIndex]\n\t\tif obj != resource {\n\t\t\tcontinue\n\t\t}\n\n\t\tsub := rule[subjectIndex]\n\n\t\tif !isRole[sub] {\n\t\t\tpermissions = append(permissions, rule)\n\t\t} else {\n\t\t\tif domain != rule[domIndex] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tusers, err := rm.GetUsers(sub, domain)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tfor _, user := range users {\n\t\t\t\timplicitUserRule := deepCopyPolicy(rule)\n\t\t\t\timplicitUserRule[subjectIndex] = user\n\t\t\t\tpermissions = append(permissions, implicitUserRule)\n\t\t\t}\n\t\t}\n\t}\n\n\tres := removeDuplicatePermissions(permissions)\n\treturn res, nil\n}\n\n// GetImplicitObjectPatternsForUser returns all object patterns (with wildcards) that a user has for a given domain and action.\n// For example:\n// p, admin, chronicle/123, location/*, read\n// p, user, chronicle/456, location/789, read\n// g, alice, admin\n// g, bob, user\n//\n// GetImplicitObjectPatternsForUser(\"alice\", \"chronicle/123\", \"read\") will return [\"location/*\"].\n// GetImplicitObjectPatternsForUser(\"bob\", \"chronicle/456\", \"read\") will return [\"location/789\"].\nfunc (e *Enforcer) GetImplicitObjectPatternsForUser(user string, domain string, action string) ([]string, error) {\n\troles, err := e.GetImplicitRolesForUser(user, domain)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsubjects := append([]string{user}, roles...)\n\tsubjectIndex, _ := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tdomainIndex, _ := e.GetFieldIndex(\"p\", constant.DomainIndex)\n\tobjectIndex, _ := e.GetFieldIndex(\"p\", constant.ObjectIndex)\n\tactionIndex, _ := e.GetFieldIndex(\"p\", constant.ActionIndex)\n\n\tpatterns := make(map[string]struct{})\n\tfor _, rule := range e.model[\"p\"][\"p\"].Policy {\n\t\tsub := rule[subjectIndex]\n\t\tmatched := false\n\t\tfor _, subject := range subjects {\n\t\t\tif sub == subject {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !e.matchDomain(domainIndex, domain, rule) {\n\t\t\tcontinue\n\t\t}\n\n\t\truleAction := rule[actionIndex]\n\t\tif ruleAction != action && ruleAction != \"*\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tobj := rule[objectIndex]\n\t\tpatterns[obj] = struct{}{}\n\t}\n\n\tresult := make([]string, 0, len(patterns))\n\tfor pattern := range patterns {\n\t\tresult = append(result, pattern)\n\t}\n\n\treturn result, nil\n}\n\n// matchDomain checks if the domain matches the rule domain using pattern matching.\nfunc (e *Enforcer) matchDomain(domainIndex int, domain string, rule []string) bool {\n\tif domainIndex < 0 || domain == \"\" {\n\t\treturn true\n\t}\n\truleDomain := rule[domainIndex]\n\tif ruleDomain == domain {\n\t\treturn true\n\t}\n\tfor _, rm := range e.rmMap {\n\t\tif rm.Match(domain, ruleDomain) {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, crm := range e.condRmMap {\n\t\tif crm.Match(domain, ruleDomain) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "rbac_api_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// rbac_api_context.go\npackage casbin\n\nimport (\n\t\"context\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/errors\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// AddRoleForUserCtx adds a role for a user with context support.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *ContextEnforcer) AddRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error) {\n\targs := []string{user, role}\n\targs = append(args, domain...)\n\treturn e.AddGroupingPolicyCtx(ctx, args)\n}\n\n// DeleteRoleForUserCtx deletes a role for a user with context support.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *ContextEnforcer) DeleteRoleForUserCtx(ctx context.Context, user string, role string, domain ...string) (bool, error) {\n\targs := []string{user, role}\n\targs = append(args, domain...)\n\treturn e.RemoveGroupingPolicyCtx(ctx, args)\n}\n\n// DeleteRolesForUserCtx deletes all roles for a user with context support.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *ContextEnforcer) DeleteRolesForUserCtx(ctx context.Context, user string, domain ...string) (bool, error) {\n\tvar args []string\n\tif len(domain) == 0 {\n\t\targs = []string{user}\n\t} else if len(domain) > 1 {\n\t\treturn false, errors.ErrDomainParameter\n\t} else {\n\t\targs = []string{user, \"\", domain[0]}\n\t}\n\treturn e.RemoveFilteredGroupingPolicyCtx(ctx, 0, args...)\n}\n\n// DeleteUserCtx deletes a user with context support.\n// Returns false if the user does not exist (aka not affected).\nfunc (e *ContextEnforcer) DeleteUserCtx(ctx context.Context, user string) (bool, error) {\n\tvar err error\n\tres1, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 0, user)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres2, err := e.RemoveFilteredPolicyCtx(ctx, subIndex, user)\n\treturn res1 || res2, err\n}\n\n// DeleteRoleCtx deletes a role with context support.\n// Returns false if the role does not exist (aka not affected).\nfunc (e *ContextEnforcer) DeleteRoleCtx(ctx context.Context, role string) (bool, error) {\n\tvar err error\n\tres1, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 0, role)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tres2, err := e.RemoveFilteredGroupingPolicyCtx(ctx, 1, role)\n\tif err != nil {\n\t\treturn res1, err\n\t}\n\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres3, err := e.RemoveFilteredPolicyCtx(ctx, subIndex, role)\n\treturn res1 || res2 || res3, err\n}\n\n// DeletePermissionCtx deletes a permission with context support.\n// Returns false if the permission does not exist (aka not affected).\nfunc (e *ContextEnforcer) DeletePermissionCtx(ctx context.Context, permission ...string) (bool, error) {\n\treturn e.RemoveFilteredPolicyCtx(ctx, 1, permission...)\n}\n\n// AddPermissionForUserCtx adds a permission for a user or role with context support.\n// Returns false if the user or role already has the permission (aka not affected).\nfunc (e *ContextEnforcer) AddPermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error) {\n\treturn e.AddPolicyCtx(ctx, util.JoinSlice(user, permission...))\n}\n\n// AddPermissionsForUserCtx adds multiple permissions for a user or role with context support.\n// Returns false if the user or role already has one of the permissions (aka not affected).\nfunc (e *ContextEnforcer) AddPermissionsForUserCtx(ctx context.Context, user string, permissions ...[]string) (bool, error) {\n\tvar rules [][]string\n\tfor _, permission := range permissions {\n\t\trules = append(rules, util.JoinSlice(user, permission...))\n\t}\n\treturn e.AddPoliciesCtx(ctx, rules)\n}\n\n// DeletePermissionForUserCtx deletes a permission for a user or role with context support.\n// Returns false if the user or role does not have the permission (aka not affected).\nfunc (e *ContextEnforcer) DeletePermissionForUserCtx(ctx context.Context, user string, permission ...string) (bool, error) {\n\treturn e.RemovePolicyCtx(ctx, util.JoinSlice(user, permission...))\n}\n\n// DeletePermissionsForUserCtx deletes permissions for a user or role with context support.\n// Returns false if the user or role does not have any permissions (aka not affected).\nfunc (e *ContextEnforcer) DeletePermissionsForUserCtx(ctx context.Context, user string) (bool, error) {\n\tsubIndex, err := e.GetFieldIndex(\"p\", constant.SubjectIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn e.RemoveFilteredPolicyCtx(ctx, subIndex, user)\n}\n"
  },
  {
    "path": "rbac_api_synced.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\n// GetRolesForUser gets the roles that a user has.\nfunc (e *SyncedEnforcer) GetRolesForUser(name string, domain ...string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetRolesForUser(name, domain...)\n}\n\n// GetUsersForRole gets the users that has a role.\nfunc (e *SyncedEnforcer) GetUsersForRole(name string, domain ...string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetUsersForRole(name, domain...)\n}\n\n// HasRoleForUser determines whether a user has a role.\nfunc (e *SyncedEnforcer) HasRoleForUser(name string, role string, domain ...string) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasRoleForUser(name, role, domain...)\n}\n\n// AddRoleForUser adds a role for a user.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *SyncedEnforcer) AddRoleForUser(user string, role string, domain ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddRoleForUser(user, role, domain...)\n}\n\n// AddRolesForUser adds roles for a user.\n// Returns false if the user already has the roles (aka not affected).\nfunc (e *SyncedEnforcer) AddRolesForUser(user string, roles []string, domain ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddRolesForUser(user, roles, domain...)\n}\n\n// DeleteRoleForUser deletes a role for a user.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *SyncedEnforcer) DeleteRoleForUser(user string, role string, domain ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteRoleForUser(user, role, domain...)\n}\n\n// DeleteRolesForUser deletes all roles for a user.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *SyncedEnforcer) DeleteRolesForUser(user string, domain ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteRolesForUser(user, domain...)\n}\n\n// DeleteUser deletes a user.\n// Returns false if the user does not exist (aka not affected).\nfunc (e *SyncedEnforcer) DeleteUser(user string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteUser(user)\n}\n\n// DeleteRole deletes a role.\n// Returns false if the role does not exist (aka not affected).\nfunc (e *SyncedEnforcer) DeleteRole(role string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteRole(role)\n}\n\n// DeletePermission deletes a permission.\n// Returns false if the permission does not exist (aka not affected).\nfunc (e *SyncedEnforcer) DeletePermission(permission ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeletePermission(permission...)\n}\n\n// AddPermissionForUser adds a permission for a user or role.\n// Returns false if the user or role already has the permission (aka not affected).\nfunc (e *SyncedEnforcer) AddPermissionForUser(user string, permission ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddPermissionForUser(user, permission...)\n}\n\n// AddPermissionsForUser adds permissions for a user or role.\n// Returns false if the user or role already has the permissions (aka not affected).\nfunc (e *SyncedEnforcer) AddPermissionsForUser(user string, permissions ...[]string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddPermissionsForUser(user, permissions...)\n}\n\n// DeletePermissionForUser deletes a permission for a user or role.\n// Returns false if the user or role does not have the permission (aka not affected).\nfunc (e *SyncedEnforcer) DeletePermissionForUser(user string, permission ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeletePermissionForUser(user, permission...)\n}\n\n// DeletePermissionsForUser deletes permissions for a user or role.\n// Returns false if the user or role does not have any permissions (aka not affected).\nfunc (e *SyncedEnforcer) DeletePermissionsForUser(user string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeletePermissionsForUser(user)\n}\n\n// GetPermissionsForUser gets permissions for a user or role.\nfunc (e *SyncedEnforcer) GetPermissionsForUser(user string, domain ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetPermissionsForUser(user, domain...)\n}\n\n// GetNamedPermissionsForUser gets permissions for a user or role by named policy.\nfunc (e *SyncedEnforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetNamedPermissionsForUser(ptype, user, domain...)\n}\n\n// HasPermissionForUser determines whether a user has a permission.\nfunc (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) (bool, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.HasPermissionForUser(user, permission...)\n}\n\n// GetImplicitRolesForUser gets implicit roles that a user has.\n// Compared to GetRolesForUser(), this function retrieves indirect roles besides direct roles.\n// For example:\n// g, alice, role:admin\n// g, role:admin, role:user\n//\n// GetRolesForUser(\"alice\") can only get: [\"role:admin\"].\n// But GetImplicitRolesForUser(\"alice\") will get: [\"role:admin\", \"role:user\"].\nfunc (e *SyncedEnforcer) GetImplicitRolesForUser(name string, domain ...string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetImplicitRolesForUser(name, domain...)\n}\n\n// GetImplicitPermissionsForUser gets implicit permissions for a user or role.\n// Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles.\n// For example:\n// p, admin, data1, read\n// p, alice, data2, read\n// g, alice, admin\n//\n// GetPermissionsForUser(\"alice\") can only get: [[\"alice\", \"data2\", \"read\"]].\n// But GetImplicitPermissionsForUser(\"alice\") will get: [[\"admin\", \"data1\", \"read\"], [\"alice\", \"data2\", \"read\"]].\nfunc (e *SyncedEnforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.GetImplicitPermissionsForUser(user, domain...)\n}\n\n// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.\n// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.\n// For example:\n// p, admin, data1, read\n// p2, admin, create\n// g, alice, admin\n//\n// GetImplicitPermissionsForUser(\"alice\") can only get: [[\"admin\", \"data1\", \"read\"]], whose policy is default policy \"p\"\n// But you can specify the named policy \"p2\" to get: [[\"admin\", \"create\"]] by    GetNamedImplicitPermissionsForUser(\"p2\",\"alice\").\nfunc (e *SyncedEnforcer) GetNamedImplicitPermissionsForUser(ptype string, gtype string, user string, domain ...string) ([][]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetNamedImplicitPermissionsForUser(ptype, gtype, user, domain...)\n}\n\n// GetImplicitUsersForPermission gets implicit users for a permission.\n// For example:\n// p, admin, data1, read\n// p, bob, data1, read\n// g, alice, admin\n//\n// GetImplicitUsersForPermission(\"data1\", \"read\") will get: [\"alice\", \"bob\"].\n// Note: only users will be returned, roles (2nd arg in \"g\") will be excluded.\nfunc (e *SyncedEnforcer) GetImplicitUsersForPermission(permission ...string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetImplicitUsersForPermission(permission...)\n}\n\n// GetImplicitObjectPatternsForUser returns all object patterns (with wildcards) that a user has for a given domain and action.\n// For example:\n// p, admin, chronicle/123, location/*, read\n// p, user, chronicle/456, location/789, read\n// g, alice, admin\n// g, bob, user\n//\n// GetImplicitObjectPatternsForUser(\"alice\", \"chronicle/123\", \"read\") will return [\"location/*\"].\n// GetImplicitObjectPatternsForUser(\"bob\", \"chronicle/456\", \"read\") will return [\"location/789\"].\nfunc (e *SyncedEnforcer) GetImplicitObjectPatternsForUser(user string, domain string, action string) ([]string, error) {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetImplicitObjectPatternsForUser(user, domain, action)\n}\n"
  },
  {
    "path": "rbac_api_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n\t\"github.com/casbin/casbin/v3/errors\"\n\tdefaultrolemanager \"github.com/casbin/casbin/v3/rbac/default-role-manager\"\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc testGetRoles(t *testing.T, e *Enforcer, res []string, name string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetRolesForUser(name, domain...)\n\tif err != nil {\n\t\tt.Error(\"Roles for \", name, \" could not be fetched: \", err.Error())\n\t}\n\tt.Log(\"Roles for \", name, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Roles for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetUsers(t *testing.T, e *Enforcer, res []string, name string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetUsersForRole(name, domain...)\n\tswitch err {\n\tcase nil:\n\t\tbreak\n\tcase errors.ErrNameNotFound:\n\t\tt.Log(\"No name found\")\n\tdefault:\n\t\tt.Error(\"Users for \", name, \" could not be fetched: \", err.Error())\n\t}\n\tt.Log(\"Users for \", name, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Users for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testHasRole(t *testing.T, e *Enforcer, name string, role string, res bool, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.HasRoleForUser(name, role, domain...)\n\tif err != nil {\n\t\tt.Error(\"HasRoleForUser returned an error: \", err.Error())\n\t}\n\tt.Log(name, \" has role \", role, \": \", myRes)\n\n\tif res != myRes {\n\t\tt.Error(name, \" has role \", role, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestRoleAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\ttestGetRoles(t, e, []string{\"data2_admin\"}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"data2_admin\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\")\n\n\ttestHasRole(t, e, \"alice\", \"data1_admin\", false)\n\ttestHasRole(t, e, \"alice\", \"data2_admin\", true)\n\n\t_, _ = e.AddRoleForUser(\"alice\", \"data1_admin\")\n\n\ttestGetRoles(t, e, []string{\"data1_admin\", \"data2_admin\"}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"data2_admin\")\n\n\t_, _ = e.DeleteRoleForUser(\"alice\", \"data1_admin\")\n\n\ttestGetRoles(t, e, []string{\"data2_admin\"}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"data2_admin\")\n\n\t_, _ = e.DeleteRolesForUser(\"alice\")\n\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"data2_admin\")\n\n\t_, _ = e.AddRoleForUser(\"alice\", \"data1_admin\")\n\t_, _ = e.DeleteUser(\"alice\")\n\n\ttestGetRoles(t, e, []string{}, \"alice\")\n\ttestGetRoles(t, e, []string{}, \"bob\")\n\ttestGetRoles(t, e, []string{}, \"data2_admin\")\n\n\t_, _ = e.AddRoleForUser(\"alice\", \"data2_admin\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t_, _ = e.DeleteRole(\"data2_admin\")\n\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data1\", \"write\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n}\n\nfunc TestRoleAPI_Domains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestHasRole(t, e, \"alice\", \"admin\", true, \"domain1\")\n\ttestHasRole(t, e, \"alice\", \"admin\", false, \"domain2\")\n\ttestGetRoles(t, e, []string{\"admin\"}, \"alice\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"bob\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\n\t_, _ = e.DeleteRoleForUser(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.AddRoleForUser(\"bob\", \"admin\", \"domain1\")\n\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain1\")\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\n\t_, _ = e.AddRoleForUser(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.DeleteRolesForUser(\"bob\", \"domain1\")\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"alice\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"bob\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\n\t_, _ = e.AddRolesForUser(\"bob\", []string{\"admin\", \"admin1\", \"admin2\"}, \"domain1\")\n\n\ttestGetRoles(t, e, []string{\"admin\", \"admin1\", \"admin2\"}, \"bob\", \"domain1\")\n\n\ttestGetPermissions(t, e, \"admin\", [][]string{{\"admin\", \"domain1\", \"data1\", \"read\"}, {\"admin\", \"domain1\", \"data1\", \"write\"}}, \"domain1\")\n\ttestGetPermissions(t, e, \"admin\", [][]string{{\"admin\", \"domain2\", \"data2\", \"read\"}, {\"admin\", \"domain2\", \"data2\", \"write\"}}, \"domain2\")\n}\n\nfunc TestEnforcer_AddRolesForUser(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\t_, _ = e.AddRolesForUser(\"alice\", []string{\"data1_admin\", \"data2_admin\", \"data3_admin\"})\n\t// The \"alice\" already has \"data2_admin\" , it will be return false. So \"alice\" just has \"data2_admin\".\n\ttestGetRoles(t, e, []string{\"data2_admin\"}, \"alice\")\n\t// delete role\n\t_, _ = e.DeleteRoleForUser(\"alice\", \"data2_admin\")\n\n\t_, _ = e.AddRolesForUser(\"alice\", []string{\"data1_admin\", \"data2_admin\", \"data3_admin\"})\n\ttestGetRoles(t, e, []string{\"data1_admin\", \"data2_admin\", \"data3_admin\"}, \"alice\")\n\ttestEnforce(t, e, \"alice\", \"data1\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"read\", true)\n\ttestEnforce(t, e, \"alice\", \"data2\", \"write\", true)\n}\n\nfunc testGetPermissions(t *testing.T, e *Enforcer, name string, res [][]string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetPermissionsForUser(name, domain...)\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\tt.Log(\"Permissions for \", name, \": \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Permissions for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testHasPermission(t *testing.T, e *Enforcer, name string, permission []string, res bool) {\n\tt.Helper()\n\tmyRes, err := e.HasPermissionForUser(name, permission...)\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\n\tt.Log(name, \" has permission \", util.ArrayToString(permission), \": \", myRes)\n\n\tif res != myRes {\n\t\tt.Error(name, \" has permission \", util.ArrayToString(permission), \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetNamedPermissionsForUser(t *testing.T, e *Enforcer, ptype string, name string, res [][]string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetNamedPermissionsForUser(ptype, name, domain...)\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\tt.Log(\"Named permissions for \", name, \": \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Named permissions for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestPermissionAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/basic_without_resources_model.conf\", \"examples/basic_without_resources_policy.csv\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", true)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n\n\ttestGetPermissions(t, e, \"alice\", [][]string{{\"alice\", \"read\"}})\n\ttestGetPermissions(t, e, \"bob\", [][]string{{\"bob\", \"write\"}})\n\n\ttestHasPermission(t, e, \"alice\", []string{\"read\"}, true)\n\ttestHasPermission(t, e, \"alice\", []string{\"write\"}, false)\n\ttestHasPermission(t, e, \"bob\", []string{\"read\"}, false)\n\ttestHasPermission(t, e, \"bob\", []string{\"write\"}, true)\n\n\t_, _ = e.DeletePermission(\"read\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n\n\t_, _ = e.AddPermissionForUser(\"bob\", \"read\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", true)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n\n\t_, _ = e.AddPermissionsForUser(\"jack\",\n\t\t[]string{\"read\"},\n\t\t[]string{\"write\"})\n\n\ttestEnforceWithoutUsers(t, e, \"jack\", \"read\", true)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n\n\t_, _ = e.DeletePermissionForUser(\"bob\", \"read\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", true)\n\n\t_, _ = e.DeletePermissionsForUser(\"bob\")\n\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"alice\", \"write\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"read\", false)\n\ttestEnforceWithoutUsers(t, e, \"bob\", \"write\", false)\n\n\te, _ = NewEnforcer(\"examples/rbac_with_multiple_policy_model.conf\", \"examples/rbac_with_multiple_policy_policy.csv\")\n\ttestGetNamedPermissionsForUser(t, e, \"p\", \"user\", [][]string{{\"user\", \"/data\", \"GET\"}})\n\ttestGetNamedPermissionsForUser(t, e, \"p2\", \"user\", [][]string{{\"user\", \"view\"}})\n}\n\nfunc testGetImplicitRoles(t *testing.T, e *Enforcer, name string, res []string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitRolesForUser(name)\n\tt.Log(\"Implicit roles for \", name, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Implicit roles for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetImplicitRolesInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitRolesForUser(name, domain)\n\tt.Log(\"Implicit roles in domain \", domain, \" for \", name, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Implicit roles in domain \", domain, \" for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestImplicitRoleAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_with_hierarchy_policy.csv\")\n\n\ttestGetPermissions(t, e, \"alice\", [][]string{{\"alice\", \"data1\", \"read\"}})\n\ttestGetPermissions(t, e, \"bob\", [][]string{{\"bob\", \"data2\", \"write\"}})\n\n\ttestGetImplicitRoles(t, e, \"alice\", []string{\"admin\", \"data1_admin\", \"data2_admin\"})\n\ttestGetImplicitRoles(t, e, \"bob\", []string{})\n\n\te, _ = NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\n\te.GetRoleManager().AddMatchingFunc(\"matcher\", util.KeyMatch)\n\te.AddNamedMatchingFunc(\"g2\", \"matcher\", util.KeyMatch)\n\n\t// testGetImplicitRoles(t, e, \"cathy\", []string{\"/book/1/2/3/4/5\", \"pen_admin\", \"/book/*\", \"book_group\"})\n\ttestGetImplicitRoles(t, e, \"cathy\", []string{\"/book/1/2/3/4/5\", \"pen_admin\"})\n\ttestGetRoles(t, e, []string{\"/book/1/2/3/4/5\", \"pen_admin\"}, \"cathy\")\n}\n\nfunc testGetImplicitPermissions(t *testing.T, e *Enforcer, name string, res [][]string, domain ...string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitPermissionsForUser(name, domain...)\n\tt.Log(\"Implicit permissions for \", name, \": \", myRes)\n\n\tif !util.Set2DEquals(res, myRes) {\n\t\tt.Error(\"Implicit permissions for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetImplicitPermissionsWithDomain(t *testing.T, e *Enforcer, name string, domain string, res [][]string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitPermissionsForUser(name, domain)\n\tt.Log(\"Implicit permissions for\", name, \"under\", domain, \":\", myRes)\n\n\tif !util.Set2DEquals(res, myRes) {\n\t\tt.Error(\"Implicit permissions for\", name, \"under\", domain, \":\", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetNamedImplicitPermissions(t *testing.T, e *Enforcer, ptype string, gtype string, name string, res [][]string) {\n\tt.Helper()\n\tmyRes, _ := e.GetNamedImplicitPermissionsForUser(ptype, gtype, name)\n\tt.Log(\"Named implicit permissions for \", name, \": \", myRes)\n\n\tif !util.Set2DEquals(res, myRes) {\n\t\tt.Error(\"Named implicit permissions for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestImplicitPermissionAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_with_hierarchy_policy.csv\")\n\n\ttestGetPermissions(t, e, \"alice\", [][]string{{\"alice\", \"data1\", \"read\"}})\n\ttestGetPermissions(t, e, \"bob\", [][]string{{\"bob\", \"data2\", \"write\"}})\n\n\ttestGetImplicitPermissions(t, e, \"alice\", [][]string{{\"alice\", \"data1\", \"read\"}, {\"data1_admin\", \"data1\", \"read\"}, {\"data1_admin\", \"data1\", \"write\"}, {\"data2_admin\", \"data2\", \"read\"}, {\"data2_admin\", \"data2\", \"write\"}})\n\ttestGetImplicitPermissions(t, e, \"bob\", [][]string{{\"bob\", \"data2\", \"write\"}})\n\n\te, _ = NewEnforcer(\"examples/rbac_with_domain_pattern_model.conf\", \"examples/rbac_with_domain_pattern_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"KeyMatch\", util.KeyMatch)\n\n\ttestGetImplicitPermissions(t, e, \"admin\", [][]string{{\"admin\", \"domain1\", \"data1\", \"read\"}, {\"admin\", \"domain1\", \"data1\", \"write\"}, {\"admin\", \"domain1\", \"data3\", \"read\"}}, \"domain1\")\n\n\t_, err := e.GetImplicitPermissionsForUser(\"admin\", \"domain1\", \"domain2\")\n\tif err == nil {\n\t\tt.Error(\"GetImplicitPermissionsForUser should not support multiple domains\")\n\t}\n\n\ttestGetImplicitPermissions(t, e, \"alice\",\n\t\t[][]string{{\"admin\", \"domain2\", \"data2\", \"read\"}, {\"admin\", \"domain2\", \"data2\", \"write\"}, {\"admin\", \"domain2\", \"data3\", \"read\"}},\n\t\t\"domain2\")\n\n\te, _ = NewEnforcer(\"examples/rbac_with_multiple_policy_model.conf\", \"examples/rbac_with_multiple_policy_policy.csv\")\n\n\ttestGetNamedImplicitPermissions(t, e, \"p\", \"g\", \"alice\", [][]string{{\"user\", \"/data\", \"GET\"}, {\"admin\", \"/data\", \"POST\"}})\n\ttestGetNamedImplicitPermissions(t, e, \"p2\", \"g\", \"alice\", [][]string{{\"user\", \"view\"}, {\"admin\", \"create\"}})\n\n\ttestGetNamedImplicitPermissions(t, e, \"p\", \"g2\", \"alice\", [][]string{{\"user\", \"/data\", \"GET\"}})\n\ttestGetNamedImplicitPermissions(t, e, \"p2\", \"g2\", \"alice\", [][]string{{\"user\", \"view\"}})\n}\n\nfunc TestImplicitPermissionAPIWithDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_hierarchy_with_domains_policy.csv\")\n\ttestGetImplicitPermissionsWithDomain(t, e, \"alice\", \"domain1\", [][]string{{\"alice\", \"domain1\", \"data2\", \"read\"}, {\"role:reader\", \"domain1\", \"data1\", \"read\"}, {\"role:writer\", \"domain1\", \"data1\", \"write\"}})\n}\n\nfunc testGetImplicitUsers(t *testing.T, e *Enforcer, res []string, permission ...string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitUsersForPermission(permission...)\n\tt.Log(\"Implicit users for permission: \", permission, \": \", myRes)\n\n\tsort.Strings(res)\n\tsort.Strings(myRes)\n\n\tif !util.ArrayEquals(res, myRes) {\n\t\tt.Error(\"Implicit users for permission: \", permission, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestImplicitUserAPI(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_with_hierarchy_policy.csv\")\n\n\ttestGetImplicitUsers(t, e, []string{\"alice\"}, \"data1\", \"read\")\n\ttestGetImplicitUsers(t, e, []string{\"alice\"}, \"data1\", \"write\")\n\ttestGetImplicitUsers(t, e, []string{\"alice\"}, \"data2\", \"read\")\n\ttestGetImplicitUsers(t, e, []string{\"alice\", \"bob\"}, \"data2\", \"write\")\n\n\te.ClearPolicy()\n\t_, _ = e.AddPolicy(\"admin\", \"data1\", \"read\")\n\t_, _ = e.AddPolicy(\"bob\", \"data1\", \"read\")\n\t_, _ = e.AddGroupingPolicy(\"alice\", \"admin\")\n\ttestGetImplicitUsers(t, e, []string{\"alice\", \"bob\"}, \"data1\", \"read\")\n}\n\nfunc testGetImplicitResourcesForUser(t *testing.T, e *Enforcer, res [][]string, user string, domain ...string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitResourcesForUser(user, domain...)\n\tt.Log(\"Implicit resources for user: \", user, \": \", myRes)\n\n\tlessFunc := func(arr [][]string) func(int, int) bool {\n\t\treturn func(i, j int) bool {\n\t\t\tpolicy1, policy2 := arr[i], arr[j]\n\t\t\tfor k := range policy1 {\n\t\t\t\tif policy1[k] == policy2[k] {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn policy1[k] < policy2[k]\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t}\n\n\tsort.Slice(res, lessFunc(res))\n\tsort.Slice(myRes, lessFunc(myRes))\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Implicit resources for user: \", user, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetImplicitResourcesForUser(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\ttestGetImplicitResourcesForUser(t, e, [][]string{\n\t\t{\"alice\", \"/pen/1\", \"GET\"},\n\t\t{\"alice\", \"/pen2/1\", \"GET\"},\n\t\t{\"alice\", \"/book/:id\", \"GET\"},\n\t\t{\"alice\", \"/book2/{id}\", \"GET\"},\n\t\t{\"alice\", \"/book/*\", \"GET\"},\n\t\t{\"alice\", \"book_group\", \"GET\"},\n\t}, \"alice\")\n\ttestGetImplicitResourcesForUser(t, e, [][]string{\n\t\t{\"bob\", \"pen_group\", \"GET\"},\n\t\t{\"bob\", \"/pen/:id\", \"GET\"},\n\t\t{\"bob\", \"/pen2/{id}\", \"GET\"},\n\t}, \"bob\")\n\ttestGetImplicitResourcesForUser(t, e, [][]string{\n\t\t{\"cathy\", \"pen_group\", \"GET\"},\n\t\t{\"cathy\", \"/pen/:id\", \"GET\"},\n\t\t{\"cathy\", \"/pen2/{id}\", \"GET\"},\n\t}, \"cathy\")\n}\n\nfunc TestImplicitUsersForRole(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\n\ttestGetImplicitUsersForRole(t, e, \"book_admin\", []string{\"alice\"})\n\ttestGetImplicitUsersForRole(t, e, \"pen_admin\", []string{\"cathy\", \"bob\"})\n\n\ttestGetImplicitUsersForRole(t, e, \"book_group\", []string{\"/book/*\", \"/book/:id\", \"/book2/{id}\"})\n\ttestGetImplicitUsersForRole(t, e, \"pen_group\", []string{\"/pen/:id\", \"/pen2/{id}\"})\n}\n\nfunc testGetImplicitUsersForRole(t *testing.T, e *Enforcer, name string, res []string) {\n\tt.Helper()\n\tmyRes, _ := e.GetImplicitUsersForRole(name)\n\tt.Log(\"Implicit users for \", name, \": \", myRes)\n\tsort.Strings(res)\n\tsort.Strings(myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Implicit users for \", name, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestExplicitPriorityModify(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/priority_model_explicit.conf\", \"examples/priority_policy_explicit.csv\")\n\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\t_, err := e.AddPolicy(\"1\", \"bob\", \"data2\", \"write\", \"deny\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPolicy: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n\n\t_, err = e.DeletePermissionsForUser(\"bob\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeletePermissionForUser: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t_, err = e.DeleteRole(\"data2_allow_group\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteRole: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n}\n\nfunc TestCustomizedFieldIndex(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/priority_model_explicit_customized.conf\",\n\t\t\"examples/priority_policy_explicit_customized.csv\")\n\n\t// Due to the customized priority token, the enforcer failed to handle the priority.\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", true)\n\n\t// set PriorityIndex and reload\n\te.SetFieldIndex(\"p\", constant.PriorityIndex, 0)\n\terr := e.LoadPolicy()\n\tif err != nil {\n\t\tt.Fatalf(\"LoadPolicy: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"read\", false)\n\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\t_, err = e.AddPolicy(\"1\", \"data2\", \"write\", \"deny\", \"bob\")\n\tif err != nil {\n\t\tt.Fatalf(\"AddPolicy: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n\n\t// Due to the customized subject token, the enforcer will raise an error before SetFieldIndex.\n\t_, err = e.DeletePermissionsForUser(\"bob\")\n\tif err == nil {\n\t\tt.Fatalf(\"Failed to warning SetFieldIndex\")\n\t}\n\n\te.SetFieldIndex(\"p\", constant.SubjectIndex, 4)\n\n\t_, err = e.DeletePermissionsForUser(\"bob\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeletePermissionForUser: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", true)\n\n\t_, err = e.DeleteRole(\"data2_allow_group\")\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteRole: %v\", err)\n\t}\n\ttestEnforce(t, e, \"bob\", \"data2\", \"write\", false)\n}\n\nfunc testGetAllowedObjectConditions(t *testing.T, e *Enforcer, user string, act string, prefix string, res []string, expectedErr error) {\n\tmyRes, actualErr := e.GetAllowedObjectConditions(user, act, prefix)\n\n\tif actualErr != expectedErr {\n\t\tt.Error(\"actual Err: \", actualErr, \", supposed to be \", expectedErr)\n\t}\n\tif actualErr == nil {\n\t\tlog.Print(\"Policy: \", myRes)\n\t\tif !util.ArrayEquals(res, myRes) {\n\t\t\tt.Error(\"Policy: \", myRes, \", supposed to be \", res)\n\t\t}\n\t}\n}\n\nfunc TestGetAllowedObjectConditions(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/object_conditions_model.conf\", \"examples/object_conditions_policy.csv\")\n\ttestGetAllowedObjectConditions(t, e, \"alice\", \"read\", \"r.obj.\", []string{\"price < 25\", \"category_id = 2\"}, nil)\n\ttestGetAllowedObjectConditions(t, e, \"admin\", \"read\", \"r.obj.\", []string{\"category_id = 2\"}, nil)\n\ttestGetAllowedObjectConditions(t, e, \"bob\", \"write\", \"r.obj.\", []string{\"author = bob\"}, nil)\n\n\t// test ErrEmptyCondition\n\ttestGetAllowedObjectConditions(t, e, \"alice\", \"write\", \"r.obj.\", []string{}, errors.ErrEmptyCondition)\n\ttestGetAllowedObjectConditions(t, e, \"bob\", \"read\", \"r.obj.\", []string{}, errors.ErrEmptyCondition)\n\n\t// test ErrObjCondition\n\t// should : e.AddPolicy(\"alice\", \"r.obj.price > 50\", \"read\")\n\tok, _ := e.AddPolicy(\"alice\", \"price > 50\", \"read\")\n\tif ok {\n\t\ttestGetAllowedObjectConditions(t, e, \"alice\", \"read\", \"r.obj.\", []string{}, errors.ErrObjCondition)\n\t}\n\n\t// test prefix\n\te.ClearPolicy()\n\terr := e.GetRoleManager().DeleteLink(\"alice\", \"admin\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tok, _ = e.AddPolicies([][]string{\n\t\t{\"alice\", \"r.book.price < 25\", \"read\"},\n\t\t{\"admin\", \"r.book.category_id = 2\", \"read\"},\n\t\t{\"bob\", \"r.book.author = bob\", \"write\"},\n\t})\n\tif ok {\n\t\ttestGetAllowedObjectConditions(t, e, \"alice\", \"read\", \"r.book.\", []string{\"price < 25\"}, nil)\n\t\ttestGetAllowedObjectConditions(t, e, \"admin\", \"read\", \"r.book.\", []string{\"category_id = 2\"}, nil)\n\t\ttestGetAllowedObjectConditions(t, e, \"bob\", \"write\", \"r.book.\", []string{\"author = bob\"}, nil)\n\t}\n}\n\nfunc testGetImplicitUsersForResource(t *testing.T, e *Enforcer, res [][]string, resource string, domain ...string) {\n\tt.Helper()\n\tmyRes, err := e.GetImplicitUsersForResource(resource)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif !util.Set2DEquals(res, myRes) {\n\t\tt.Error(\"Implicit users for \", resource, \"in domain \", domain, \" : \", myRes, \", supposed to be \", res)\n\t} else {\n\t\tt.Log(\"Implicit users for \", resource, \"in domain \", domain, \" : \", myRes)\n\t}\n}\n\nfunc TestGetImplicitUsersForResource(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\ttestGetImplicitUsersForResource(t, e, [][]string{{\"alice\", \"data1\", \"read\"}}, \"data1\")\n\ttestGetImplicitUsersForResource(t, e, [][]string{{\"bob\", \"data2\", \"write\"},\n\t\t{\"alice\", \"data2\", \"read\"},\n\t\t{\"alice\", \"data2\", \"write\"}}, \"data2\")\n\n\t// test duplicate permissions\n\t_, _ = e.AddGroupingPolicy(\"alice\", \"data2_admin_2\")\n\t_, _ = e.AddPolicies([][]string{{\"data2_admin_2\", \"data2\", \"read\"}, {\"data2_admin_2\", \"data2\", \"write\"}})\n\ttestGetImplicitUsersForResource(t, e, [][]string{{\"bob\", \"data2\", \"write\"},\n\t\t{\"alice\", \"data2\", \"read\"},\n\t\t{\"alice\", \"data2\", \"write\"}}, \"data2\")\n}\n\nfunc TestGetImplicitUsersForResourceWithResourceRoles(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_resource_roles_model.conf\", \"examples/rbac_with_resource_roles_policy.csv\")\n\n\t// Test data1 resource - should return users who have access through g2 relationships\n\tdata1Users, err := e.GetNamedImplicitUsersForResource(\"g2\", \"data1\")\n\tif err != nil {\n\t\tt.Fatalf(\"GetNamedImplicitUsersForResource failed: %v\", err)\n\t}\n\n\texpectedData1Users := 2 // [alice data1 read] + [alice data_group write]\n\tif len(data1Users) != expectedData1Users {\n\t\tt.Errorf(\"Expected %d users for data1 resource, got %d: %v\", expectedData1Users, len(data1Users), data1Users)\n\t}\n\n\t// Test data2 resource - should return users who have access through g2 relationships\n\tdata2Users, err := e.GetNamedImplicitUsersForResource(\"g2\", \"data2\")\n\tif err != nil {\n\t\tt.Fatalf(\"GetNamedImplicitUsersForResource failed: %v\", err)\n\t}\n\n\texpectedData2Users := 2 // [bob data2 write] + [alice data_group write]\n\tif len(data2Users) != expectedData2Users {\n\t\tt.Errorf(\"Expected %d users for data2 resource, got %d: %v\", expectedData2Users, len(data2Users), data2Users)\n\t}\n\n\t// Test with \"g\" policy type - should return users who have access through g relationships\n\tdata1UsersG, err := e.GetNamedImplicitUsersForResource(\"g\", \"data1\")\n\tif err != nil {\n\t\tt.Fatalf(\"GetNamedImplicitUsersForResource with g failed: %v\", err)\n\t}\n\n\texpectedData1UsersG := 1 // [alice data1 read] only\n\tif len(data1UsersG) != expectedData1UsersG {\n\t\tt.Errorf(\"Expected %d users for data1 resource with g policy, got %d: %v\", expectedData1UsersG, len(data1UsersG), data1UsersG)\n\t}\n}\n\nfunc testGetImplicitUsersForResourceByDomain(t *testing.T, e *Enforcer, res [][]string, resource string, domain string) {\n\tt.Helper()\n\tmyRes, err := e.GetImplicitUsersForResourceByDomain(resource, domain)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tif !util.Set2DEquals(res, myRes) {\n\t\tt.Error(\"Implicit users for \", resource, \"in domain \", domain, \" : \", myRes, \", supposed to be \", res)\n\t} else {\n\t\tt.Log(\"Implicit users for \", resource, \"in domain \", domain, \" : \", myRes)\n\t}\n}\n\nfunc TestGetImplicitUsersForResourceByDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\ttestGetImplicitUsersForResourceByDomain(t, e, [][]string{{\"alice\", \"domain1\", \"data1\", \"read\"},\n\t\t{\"alice\", \"domain1\", \"data1\", \"write\"}}, \"data1\", \"domain1\")\n\n\ttestGetImplicitUsersForResourceByDomain(t, e, [][]string{}, \"data2\", \"domain1\")\n\n\ttestGetImplicitUsersForResourceByDomain(t, e, [][]string{{\"bob\", \"domain2\", \"data2\", \"read\"},\n\t\t{\"bob\", \"domain2\", \"data2\", \"write\"}}, \"data2\", \"domain2\")\n}\n\nfunc TestConditional(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_conditional_model.conf\", \"examples/rbac_with_domains_conditional_policy.csv\")\n\tg, _ := e.GetNamedGroupingPolicy(\"g\")\n\tfor _, gp := range g {\n\t\te.AddNamedDomainLinkConditionFunc(\"g\", gp[0], gp[1], gp[2], util.TimeMatchFunc)\n\t}\n\n\ttestDomainEnforce(t, e, \"alice\", \"domain1\", \"service1\", \"/list\", true)\n\ttestDomainEnforce(t, e, \"bob\", \"domain2\", \"service2\", \"/broadcast\", true)\n\ttestDomainEnforce(t, e, \"jack\", \"domain1\", \"service1\", \"/list\", false)\n\ttestGetImplicitRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"test1\"})\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"test1\"})\n\ttestGetUsersInDomain(t, e, \"test1\", \"domain1\", []string{\"alice\"})\n}\n\nfunc TestMaxHierarchyLevelConsistency(t *testing.T) {\n\t// Test consistency behavior under different maxHierarchyLevel values\n\ttestCases := []struct {\n\t\tmaxLevel int\n\t\tname     string\n\t}{\n\t\t{1, \"maxHierarchyLevel=1\"},\n\t\t{2, \"maxHierarchyLevel=2\"},\n\t\t{3, \"maxHierarchyLevel=3\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Use model files from examples\n\t\t\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t\t\t}\n\n\t\t\t// Set the maximum hierarchy level for role manager\n\t\t\trm := defaultrolemanager.NewRoleManager(tc.maxLevel)\n\t\t\te.SetRoleManager(rm)\n\n\t\t\t// Add role hierarchy: level0 -> level1 -> level2 -> level3 -> level4\n\t\t\t_, err = e.AddRoleForUser(\"level0\", \"level1\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to add role for user: %v\", err)\n\t\t\t}\n\t\t\t_, err = e.AddRoleForUser(\"level1\", \"level2\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to add role for user: %v\", err)\n\t\t\t}\n\t\t\t_, err = e.AddRoleForUser(\"level2\", \"level3\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to add role for user: %v\", err)\n\t\t\t}\n\t\t\t_, err = e.AddRoleForUser(\"level3\", \"level4\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to add role for user: %v\", err)\n\t\t\t}\n\n\t\t\t// Test HasLink method\n\t\t\tt.Run(\"HasLink\", func(t *testing.T) {\n\t\t\t\tfor i := 1; i <= 4; i++ {\n\t\t\t\t\thasLink, err := rm.HasLink(\"level0\", fmt.Sprintf(\"level%d\", i))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"HasLink error: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\texpected := i <= tc.maxLevel\n\t\t\t\t\tif hasLink != expected {\n\t\t\t\t\t\tt.Errorf(\"HasLink(level0, level%d): got %v, want %v\", i, hasLink, expected)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Test GetImplicitRolesForUser method\n\t\t\tt.Run(\"GetImplicitRolesForUser\", func(t *testing.T) {\n\t\t\t\timplicitRoles, err := e.GetImplicitRolesForUser(\"level0\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"GetImplicitRolesForUser error: %v\", err)\n\t\t\t\t}\n\n\t\t\t\texpectedCount := tc.maxLevel\n\t\t\t\tif len(implicitRoles) != expectedCount {\n\t\t\t\t\tt.Errorf(\"GetImplicitRolesForUser(level0): got %d roles %v, want %d roles\",\n\t\t\t\t\t\tlen(implicitRoles), implicitRoles, expectedCount)\n\t\t\t\t}\n\n\t\t\t\t// Verify that returned roles are correct\n\t\t\t\tfor i := 1; i <= tc.maxLevel; i++ {\n\t\t\t\t\texpectedRole := fmt.Sprintf(\"level%d\", i)\n\t\t\t\t\tfound := false\n\t\t\t\t\tfor _, role := range implicitRoles {\n\t\t\t\t\t\tif role == expectedRole {\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"Expected role %s not found in implicit roles: %v\", expectedRole, implicitRoles)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Test GetImplicitUsersForRole method\n\t\t\tt.Run(\"GetImplicitUsersForRole\", func(t *testing.T) {\n\t\t\t\timplicitUsers, err := e.GetImplicitUsersForRole(\"level4\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"GetImplicitUsersForRole error: %v\", err)\n\t\t\t\t}\n\n\t\t\t\texpectedCount := tc.maxLevel\n\t\t\t\tif len(implicitUsers) != expectedCount {\n\t\t\t\t\tt.Errorf(\"GetImplicitUsersForRole(level4): got %d users %v, want %d users\",\n\t\t\t\t\t\tlen(implicitUsers), implicitUsers, expectedCount)\n\t\t\t\t}\n\n\t\t\t\t// Verify that returned users are correct (starting from level3 upward)\n\t\t\t\tfor i := 0; i < tc.maxLevel; i++ {\n\t\t\t\t\texpectedUser := fmt.Sprintf(\"level%d\", 3-i)\n\t\t\t\t\tfound := false\n\t\t\t\t\tfor _, user := range implicitUsers {\n\t\t\t\t\t\tif user == expectedUser {\n\t\t\t\t\t\t\tfound = true\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"Expected user %s not found in implicit users: %v\", expectedUser, implicitUsers)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// Test implicit roles for different users\n\t\t\tt.Run(\"DifferentUsersImplicitRoles\", func(t *testing.T) {\n\t\t\t\tfor i := 0; i <= 3; i++ {\n\t\t\t\t\tuser := fmt.Sprintf(\"level%d\", i)\n\t\t\t\t\timplicitRoles, err := e.GetImplicitRolesForUser(user)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"GetImplicitRolesForUser(%s) error: %v\", user, err)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Verify that the number of returned roles does not exceed maxHierarchyLevel\n\t\t\t\t\tif len(implicitRoles) > tc.maxLevel {\n\t\t\t\t\t\tt.Errorf(\"GetImplicitRolesForUser(%s): got %d roles, should not exceed maxHierarchyLevel %d\",\n\t\t\t\t\t\t\tuser, len(implicitRoles), tc.maxLevel)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nfunc testGetImplicitObjectPatternsForUser(t *testing.T, e *Enforcer, user string, domain string, action string, res []string) {\n\tt.Helper()\n\tmyRes, err := e.GetImplicitObjectPatternsForUser(user, domain, action)\n\tif err != nil {\n\t\tt.Error(\"Implicit object patterns for \", user, \" under domain \", domain, \" with action \", action, \" could not be fetched: \", err.Error())\n\t}\n\tt.Log(\"Implicit object patterns for \", user, \" under domain \", domain, \" with action \", action, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Implicit object patterns for \", user, \" under domain \", domain, \" with action \", action, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetImplicitObjectPatternsForUser(t *testing.T) {\n\t// Test with domain pattern model\n\te, _ := NewEnforcer(\"examples/rbac_with_domain_pattern_model.conf\", \"examples/rbac_with_domain_pattern_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"KeyMatch\", util.KeyMatch)\n\n\t// Test case 1: admin user with wildcard domain access\n\ttestGetImplicitObjectPatternsForUser(t, e, \"admin\", \"domain1\", \"read\", []string{\"data1\", \"data3\"})\n\ttestGetImplicitObjectPatternsForUser(t, e, \"admin\", \"domain1\", \"write\", []string{\"data1\"})\n\n\t// Test case 2: alice user inheriting admin role in domain2\n\ttestGetImplicitObjectPatternsForUser(t, e, \"alice\", \"domain2\", \"read\", []string{\"data2\", \"data3\"})\n\ttestGetImplicitObjectPatternsForUser(t, e, \"alice\", \"domain2\", \"write\", []string{\"data2\"})\n\n\t// Test case 3: bob user with specific domain access\n\ttestGetImplicitObjectPatternsForUser(t, e, \"bob\", \"domain2\", \"read\", []string{\"data2\", \"data3\"})\n\ttestGetImplicitObjectPatternsForUser(t, e, \"bob\", \"domain2\", \"write\", []string{\"data2\"})\n\n\t// Test case 4: non-existent domain (admin has wildcard access to data3)\n\ttestGetImplicitObjectPatternsForUser(t, e, \"admin\", \"non_existent\", \"read\", []string{\"data3\"})\n\n\t// Test case 5: non-existent action\n\ttestGetImplicitObjectPatternsForUser(t, e, \"admin\", \"domain1\", \"non_existent\", []string{})\n}\n"
  },
  {
    "path": "rbac_api_with_domains.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n)\n\n// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon.\nfunc (e *Enforcer) GetUsersForRoleInDomain(name string, domain string) []string {\n\tif e.GetRoleManager() == nil {\n\t\treturn nil\n\t}\n\tres, _ := e.GetRoleManager().GetUsers(name, domain)\n\treturn res\n}\n\n// GetRolesForUserInDomain gets the roles that a user has inside a domain.\nfunc (e *Enforcer) GetRolesForUserInDomain(name string, domain string) []string {\n\tif rm := e.GetRoleManager(); rm != nil {\n\t\tres, _ := rm.GetRoles(name, domain)\n\t\treturn res\n\t}\n\treturn nil\n}\n\n// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain.\nfunc (e *Enforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string {\n\tres, _ := e.GetImplicitPermissionsForUser(user, domain)\n\treturn res\n}\n\n// AddRoleForUserInDomain adds a role for a user inside a domain.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *Enforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) {\n\treturn e.AddGroupingPolicy(user, role, domain)\n}\n\n// DeleteRoleForUserInDomain deletes a role for a user inside a domain.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *Enforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) {\n\treturn e.RemoveGroupingPolicy(user, role, domain)\n}\n\n// DeleteRolesForUserInDomain deletes all roles for a user inside a domain.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *Enforcer) DeleteRolesForUserInDomain(user string, domain string) (bool, error) {\n\tif e.GetRoleManager() == nil {\n\t\treturn false, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\troles, err := e.GetRoleManager().GetRoles(user, domain)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar rules [][]string\n\tfor _, role := range roles {\n\t\trules = append(rules, []string{user, role, domain})\n\t}\n\n\treturn e.RemoveGroupingPolicies(rules)\n}\n\n// GetAllUsersByDomain would get all users associated with the domain.\nfunc (e *Enforcer) GetAllUsersByDomain(domain string) ([]string, error) {\n\tm := make(map[string]struct{})\n\tg, err := e.model.GetAssertion(\"g\", \"g\")\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\tp := e.model[\"p\"][\"p\"]\n\tusers := make([]string, 0)\n\tindex, err := e.GetFieldIndex(\"p\", constant.DomainIndex)\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\n\tgetUser := func(index int, policies [][]string, domain string, m map[string]struct{}) []string {\n\t\tif len(policies) == 0 || len(policies[0]) <= index {\n\t\t\treturn []string{}\n\t\t}\n\t\tres := make([]string, 0)\n\t\tfor _, policy := range policies {\n\t\t\tif _, ok := m[policy[0]]; policy[index] == domain && !ok {\n\t\t\t\tres = append(res, policy[0])\n\t\t\t\tm[policy[0]] = struct{}{}\n\t\t\t}\n\t\t}\n\t\treturn res\n\t}\n\n\tusers = append(users, getUser(2, g.Policy, domain, m)...)\n\tusers = append(users, getUser(index, p.Policy, domain, m)...)\n\treturn users, nil\n}\n\n// DeleteAllUsersByDomain would delete all users associated with the domain.\nfunc (e *Enforcer) DeleteAllUsersByDomain(domain string) (bool, error) {\n\tg, err := e.model.GetAssertion(\"g\", \"g\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tp := e.model[\"p\"][\"p\"]\n\tindex, err := e.GetFieldIndex(\"p\", constant.DomainIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tgetUser := func(index int, policies [][]string, domain string) [][]string {\n\t\tif len(policies) == 0 || len(policies[0]) <= index {\n\t\t\treturn [][]string{}\n\t\t}\n\t\tres := make([][]string, 0)\n\t\tfor _, policy := range policies {\n\t\t\tif policy[index] == domain {\n\t\t\t\tres = append(res, policy)\n\t\t\t}\n\t\t}\n\t\treturn res\n\t}\n\n\tusers := getUser(2, g.Policy, domain)\n\tif _, err = e.RemoveGroupingPolicies(users); err != nil {\n\t\treturn false, err\n\t}\n\tusers = getUser(index, p.Policy, domain)\n\tif _, err = e.RemovePolicies(users); err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// DeleteDomains would delete all associated policies.\n// It would delete all domains if parameter is not provided.\nfunc (e *Enforcer) DeleteDomains(domains ...string) (bool, error) {\n\tif len(domains) == 0 {\n\t\tvar err error\n\t\tdomains, err = e.GetAllDomains()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\tfor _, domain := range domains {\n\t\tif _, err := e.DeleteAllUsersByDomain(domain); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\t// remove the domain from the RoleManager.\n\t\tif e.GetRoleManager() != nil {\n\t\t\tif err := e.GetRoleManager().DeleteDomain(domain); err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\treturn true, nil\n}\n\n// GetAllDomains would get all domains.\nfunc (e *Enforcer) GetAllDomains() ([]string, error) {\n\tif e.GetRoleManager() == nil {\n\t\treturn nil, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\treturn e.GetRoleManager().GetAllDomains()\n}\n\n// GetAllRolesByDomain would get all roles associated with the domain.\n// note: Not applicable to Domains with inheritance relationship  (implicit roles)\nfunc (e *Enforcer) GetAllRolesByDomain(domain string) ([]string, error) {\n\tg, err := e.model.GetAssertion(\"g\", \"g\")\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\tpolicies := g.Policy\n\troles := make([]string, 0)\n\texistMap := make(map[string]bool) // remove duplicates\n\n\tfor _, policy := range policies {\n\t\tif policy[len(policy)-1] == domain {\n\t\t\trole := policy[len(policy)-2]\n\t\t\tif _, ok := existMap[role]; !ok {\n\t\t\t\troles = append(roles, role)\n\t\t\t\texistMap[role] = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn roles, nil\n}\n"
  },
  {
    "path": "rbac_api_with_domains_context.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/casbin/casbin/v3/constant\"\n)\n\n// AddRoleForUserInDomainCtx adds a role for a user inside a domain with context support.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *ContextEnforcer) AddRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error) {\n\treturn e.AddGroupingPolicyCtx(ctx, user, role, domain)\n}\n\n// DeleteRoleForUserInDomainCtx deletes a role for a user inside a domain with context support.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *ContextEnforcer) DeleteRoleForUserInDomainCtx(ctx context.Context, user string, role string, domain string) (bool, error) {\n\treturn e.RemoveGroupingPolicyCtx(ctx, user, role, domain)\n}\n\n// DeleteRolesForUserInDomainCtx deletes all roles for a user inside a domain with context support.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *ContextEnforcer) DeleteRolesForUserInDomainCtx(ctx context.Context, user string, domain string) (bool, error) {\n\tif e.GetRoleManager() == nil {\n\t\treturn false, fmt.Errorf(\"role manager is not initialized\")\n\t}\n\troles, err := e.GetRoleManager().GetRoles(user, domain)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar rules [][]string\n\tfor _, role := range roles {\n\t\trules = append(rules, []string{user, role, domain})\n\t}\n\n\treturn e.RemoveGroupingPoliciesCtx(ctx, rules)\n}\n\n// DeleteAllUsersByDomainCtx deletes all users associated with the domain with context support.\nfunc (e *ContextEnforcer) DeleteAllUsersByDomainCtx(ctx context.Context, domain string) (bool, error) {\n\tg, err := e.model.GetAssertion(\"g\", \"g\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tp := e.model[\"p\"][\"p\"]\n\tindex, err := e.GetFieldIndex(\"p\", constant.DomainIndex)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tgetUser := func(index int, policies [][]string, domain string) [][]string {\n\t\tif len(policies) == 0 || len(policies[0]) <= index {\n\t\t\treturn [][]string{}\n\t\t}\n\t\tres := make([][]string, 0)\n\t\tfor _, policy := range policies {\n\t\t\tif policy[index] == domain {\n\t\t\t\tres = append(res, policy)\n\t\t\t}\n\t\t}\n\t\treturn res\n\t}\n\n\tusers := getUser(2, g.Policy, domain)\n\tif _, err = e.RemoveGroupingPoliciesCtx(ctx, users); err != nil {\n\t\treturn false, err\n\t}\n\tusers = getUser(index, p.Policy, domain)\n\tif _, err = e.RemovePoliciesCtx(ctx, users); err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// DeleteDomainsCtx deletes all associated policies for domains with context support.\n// It would delete all domains if parameter is not provided.\nfunc (e *ContextEnforcer) DeleteDomainsCtx(ctx context.Context, domains ...string) (bool, error) {\n\tif len(domains) == 0 {\n\t\tvar err error\n\t\tdomains, err = e.GetAllDomains()\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\tfor _, domain := range domains {\n\t\tif _, err := e.DeleteAllUsersByDomainCtx(ctx, domain); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\t// remove the domain from the RoleManager.\n\t\tif e.GetRoleManager() != nil {\n\t\t\tif err := e.GetRoleManager().DeleteDomain(domain); err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t}\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "rbac_api_with_domains_synced.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\n// GetUsersForRoleInDomain gets the users that has a role inside a domain. Add by Gordon.\nfunc (e *SyncedEnforcer) GetUsersForRoleInDomain(name string, domain string) []string {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetUsersForRoleInDomain(name, domain)\n}\n\n// GetRolesForUserInDomain gets the roles that a user has inside a domain.\nfunc (e *SyncedEnforcer) GetRolesForUserInDomain(name string, domain string) []string {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetRolesForUserInDomain(name, domain)\n}\n\n// GetPermissionsForUserInDomain gets permissions for a user or role inside a domain.\nfunc (e *SyncedEnforcer) GetPermissionsForUserInDomain(user string, domain string) [][]string {\n\te.m.RLock()\n\tdefer e.m.RUnlock()\n\treturn e.Enforcer.GetPermissionsForUserInDomain(user, domain)\n}\n\n// AddRoleForUserInDomain adds a role for a user inside a domain.\n// Returns false if the user already has the role (aka not affected).\nfunc (e *SyncedEnforcer) AddRoleForUserInDomain(user string, role string, domain string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.AddRoleForUserInDomain(user, role, domain)\n}\n\n// DeleteRoleForUserInDomain deletes a role for a user inside a domain.\n// Returns false if the user does not have the role (aka not affected).\nfunc (e *SyncedEnforcer) DeleteRoleForUserInDomain(user string, role string, domain string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteRoleForUserInDomain(user, role, domain)\n}\n\n// DeleteRolesForUserInDomain deletes all roles for a user inside a domain.\n// Returns false if the user does not have any roles (aka not affected).\nfunc (e *SyncedEnforcer) DeleteRolesForUserInDomain(user string, domain string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteRolesForUserInDomain(user, domain)\n}\n\n// DeleteDomains deletes domains from the model.\n// Returns false if the domain does not exist (aka not affected).\nfunc (e *SyncedEnforcer) DeleteDomains(domains ...string) (bool, error) {\n\te.m.Lock()\n\tdefer e.m.Unlock()\n\treturn e.Enforcer.DeleteDomains(domains...)\n}\n"
  },
  {
    "path": "rbac_api_with_domains_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\n// testGetUsersInDomain: Add by Gordon.\nfunc testGetUsersInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {\n\tt.Helper()\n\tmyRes := e.GetUsersForRoleInDomain(name, domain)\n\tt.Log(\"Users for \", name, \" under \", domain, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Users for \", name, \" under \", domain, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc testGetRolesInDomain(t *testing.T, e *Enforcer, name string, domain string, res []string) {\n\tt.Helper()\n\tmyRes := e.GetRolesForUserInDomain(name, domain)\n\tt.Log(\"Roles for \", name, \" under \", domain, \": \", myRes)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"Roles for \", name, \" under \", domain, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetImplicitRolesForDomainUser(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_hierarchy_with_domains_policy.csv\")\n\n\t// This is only able to retrieve the first level of roles.\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"role:global_admin\"})\n\n\t// Retrieve all inherit roles. It supports domains as well.\n\ttestGetImplicitRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"role:global_admin\", \"role:reader\", \"role:writer\"})\n}\n\n// TestUserAPIWithDomains: Add by Gordon.\nfunc TestUserAPIWithDomains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetUsers(t, e, []string{\"alice\"}, \"admin\", \"domain1\")\n\ttestGetUsersInDomain(t, e, \"admin\", \"domain1\", []string{\"alice\"})\n\n\ttestGetUsers(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetUsersInDomain(t, e, \"non_exist\", \"domain1\", []string{})\n\n\ttestGetUsers(t, e, []string{\"bob\"}, \"admin\", \"domain2\")\n\ttestGetUsersInDomain(t, e, \"admin\", \"domain2\", []string{\"bob\"})\n\n\ttestGetUsers(t, e, []string{}, \"non_exist\", \"domain2\")\n\ttestGetUsersInDomain(t, e, \"non_exist\", \"domain2\", []string{})\n\n\t_, _ = e.DeleteRoleForUserInDomain(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.AddRoleForUserInDomain(\"bob\", \"admin\", \"domain1\")\n\n\ttestGetUsers(t, e, []string{\"bob\"}, \"admin\", \"domain1\")\n\ttestGetUsersInDomain(t, e, \"admin\", \"domain1\", []string{\"bob\"})\n\n\ttestGetUsers(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetUsersInDomain(t, e, \"non_exist\", \"domain1\", []string{})\n\n\ttestGetUsers(t, e, []string{\"bob\"}, \"admin\", \"domain2\")\n\ttestGetUsersInDomain(t, e, \"admin\", \"domain2\", []string{\"bob\"})\n\n\ttestGetUsers(t, e, []string{}, \"non_exist\", \"domain2\")\n\ttestGetUsersInDomain(t, e, \"non_exist\", \"domain2\", []string{})\n}\n\nfunc TestRoleAPIWithDomains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"alice\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"bob\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain2\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain2\", []string{})\n\n\t_, _ = e.DeleteRoleForUserInDomain(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.AddRoleForUserInDomain(\"bob\", \"admin\", \"domain1\")\n\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain1\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain2\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain2\", []string{})\n\n\t_, _ = e.AddRoleForUserInDomain(\"alice\", \"admin\", \"domain1\")\n\t_, _ = e.DeleteRolesForUserInDomain(\"bob\", \"domain1\")\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"alice\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain1\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"bob\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain1\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain1\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"alice\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"alice\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{\"admin\"}, \"bob\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"bob\", \"domain2\", []string{\"admin\"})\n\n\ttestGetRoles(t, e, []string{}, \"admin\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"admin\", \"domain2\", []string{})\n\n\ttestGetRoles(t, e, []string{}, \"non_exist\", \"domain2\")\n\ttestGetRolesInDomain(t, e, \"non_exist\", \"domain2\", []string{})\n}\n\nfunc testGetPermissionsInDomain(t *testing.T, e *Enforcer, name string, domain string, res [][]string) {\n\tt.Helper()\n\tmyRes := e.GetPermissionsForUserInDomain(name, domain)\n\tt.Log(\"Permissions for \", name, \" under \", domain, \": \", myRes)\n\n\tif !util.Array2DEquals(res, myRes) {\n\t\tt.Error(\"Permissions for \", name, \" under \", domain, \": \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestPermissionAPIInDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetPermissionsInDomain(t, e, \"alice\", \"domain1\", [][]string{{\"admin\", \"domain1\", \"data1\", \"read\"}, {\"admin\", \"domain1\", \"data1\", \"write\"}})\n\ttestGetPermissionsInDomain(t, e, \"bob\", \"domain1\", [][]string{})\n\ttestGetPermissionsInDomain(t, e, \"admin\", \"domain1\", [][]string{{\"admin\", \"domain1\", \"data1\", \"read\"}, {\"admin\", \"domain1\", \"data1\", \"write\"}})\n\ttestGetPermissionsInDomain(t, e, \"non_exist\", \"domain1\", [][]string{})\n\n\ttestGetPermissionsInDomain(t, e, \"alice\", \"domain2\", [][]string{})\n\ttestGetPermissionsInDomain(t, e, \"bob\", \"domain2\", [][]string{{\"admin\", \"domain2\", \"data2\", \"read\"}, {\"admin\", \"domain2\", \"data2\", \"write\"}})\n\ttestGetPermissionsInDomain(t, e, \"admin\", \"domain2\", [][]string{{\"admin\", \"domain2\", \"data2\", \"read\"}, {\"admin\", \"domain2\", \"data2\", \"write\"}})\n\ttestGetPermissionsInDomain(t, e, \"non_exist\", \"domain2\", [][]string{})\n}\n\nfunc testGetDomainsForUser(t *testing.T, e *Enforcer, res []string, user string) {\n\tt.Helper()\n\tmyRes, _ := e.GetDomainsForUser(user)\n\n\tsort.Strings(myRes)\n\tsort.Strings(res)\n\n\tif !util.SetEquals(res, myRes) {\n\t\tt.Error(\"domains for user: \", user, \": \", myRes, \",  supposed to be \", res)\n\t}\n}\n\nfunc TestGetDomainsForUser(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy2.csv\")\n\n\ttestGetDomainsForUser(t, e, []string{\"domain1\", \"domain2\"}, \"alice\")\n\ttestGetDomainsForUser(t, e, []string{\"domain2\", \"domain3\"}, \"bob\")\n\ttestGetDomainsForUser(t, e, []string{\"domain3\"}, \"user\")\n}\n\nfunc testGetAllUsersByDomain(t *testing.T, e *Enforcer, domain string, expected []string) {\n\tusers, _ := e.GetAllUsersByDomain(domain)\n\tif !util.SetEquals(users, expected) {\n\t\tt.Errorf(\"users in %s: %v, supposed to be %v\\n\", domain, users, expected)\n\t}\n}\n\nfunc TestGetAllUsersByDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetAllUsersByDomain(t, e, \"domain1\", []string{\"alice\", \"admin\"})\n\ttestGetAllUsersByDomain(t, e, \"domain2\", []string{\"bob\", \"admin\"})\n}\n\nfunc testDeleteAllUsersByDomain(t *testing.T, domain string, expectedPolicy, expectedGroupingPolicy [][]string) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\t_, _ = e.DeleteAllUsersByDomain(domain)\n\tpolicy, err := e.GetPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !util.Array2DEquals(policy, expectedPolicy) {\n\t\tt.Errorf(\"policy in %s: %v, supposed to be %v\\n\", domain, policy, expectedPolicy)\n\t}\n\n\tpolicies, err := e.GetGroupingPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !util.Array2DEquals(policies, expectedGroupingPolicy) {\n\t\tt.Errorf(\"grouping policy in %s: %v, supposed to be %v\\n\", domain, policies, expectedGroupingPolicy)\n\t}\n}\n\nfunc TestDeleteAllUsersByDomain(t *testing.T) {\n\ttestDeleteAllUsersByDomain(t, \"domain1\", [][]string{\n\t\t{\"admin\", \"domain2\", \"data2\", \"read\"},\n\t\t{\"admin\", \"domain2\", \"data2\", \"write\"},\n\t}, [][]string{\n\t\t{\"bob\", \"admin\", \"domain2\"},\n\t})\n\ttestDeleteAllUsersByDomain(t, \"domain2\", [][]string{\n\t\t{\"admin\", \"domain1\", \"data1\", \"read\"},\n\t\t{\"admin\", \"domain1\", \"data1\", \"write\"},\n\t}, [][]string{\n\t\t{\"alice\", \"admin\", \"domain1\"},\n\t})\n}\n\n// testGetAllDomains tests GetAllDomains().\nfunc testGetAllDomains(t *testing.T, e *Enforcer, res []string) {\n\tt.Helper()\n\tmyRes, _ := e.GetAllDomains()\n\tsort.Strings(myRes)\n\tsort.Strings(res)\n\tif !util.ArrayEquals(res, myRes) {\n\t\tt.Error(\"domains: \", myRes, \", supposed to be \", res)\n\t}\n}\n\nfunc TestGetAllDomains(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetAllDomains(t, e, []string{\"domain1\", \"domain2\"})\n}\n\nfunc testGetAllRolesByDomain(t *testing.T, e *Enforcer, domain string, expected []string) {\n\troles, _ := e.GetAllRolesByDomain(domain)\n\tif !util.SetEquals(roles, expected) {\n\t\tt.Errorf(\"roles in %s: %v, supposed to be %v\\n\", domain, roles, expected)\n\t}\n}\n\nfunc TestGetAllRolesByDomain(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\ttestGetAllRolesByDomain(t, e, \"domain1\", []string{\"admin\"})\n\ttestGetAllRolesByDomain(t, e, \"domain2\", []string{\"admin\"})\n\n\te, _ = NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy2.csv\")\n\n\ttestGetAllRolesByDomain(t, e, \"domain1\", []string{\"admin\"})\n\ttestGetAllRolesByDomain(t, e, \"domain2\", []string{\"admin\"})\n\ttestGetAllRolesByDomain(t, e, \"domain3\", []string{\"user\"})\n}\n\nfunc testDeleteDomains(t *testing.T, domains []string, expectedPolicy, expectedGroupingPolicy [][]string, expectedDomains []string) {\n\te, _ := NewEnforcer(\"examples/rbac_with_domains_model.conf\", \"examples/rbac_with_domains_policy.csv\")\n\n\t_, _ = e.DeleteDomains(domains...)\n\tpolicy, err := e.GetPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !util.Array2DEquals(policy, expectedPolicy) {\n\t\tt.Errorf(\"policy after deleting domains %v: %v, supposed to be %v\\n\", domains, policy, expectedPolicy)\n\t}\n\n\tpolicies, err := e.GetGroupingPolicy()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif !util.Array2DEquals(policies, expectedGroupingPolicy) {\n\t\tt.Errorf(\"grouping policy after deleting domains %v: %v, supposed to be %v\\n\", domains, policies, expectedGroupingPolicy)\n\t}\n\n\tdomainsAfterRemoval, _ := e.GetAllDomains()\n\tif !util.SetEquals(domainsAfterRemoval, expectedDomains) {\n\t\tt.Errorf(\"domains after deleting %v: %v, supposed to be %v\\n\", domains, domainsAfterRemoval, expectedDomains)\n\t}\n}\n\nfunc TestDeleteDomains(t *testing.T) {\n\ttestDeleteDomains(t, []string{\"domain1\"}, [][]string{\n\t\t{\"admin\", \"domain2\", \"data2\", \"read\"},\n\t\t{\"admin\", \"domain2\", \"data2\", \"write\"},\n\t}, [][]string{\n\t\t{\"bob\", \"admin\", \"domain2\"},\n\t}, []string{\"domain2\"})\n\n\ttestDeleteDomains(t, []string{\"domain2\"}, [][]string{\n\t\t{\"admin\", \"domain1\", \"data1\", \"read\"},\n\t\t{\"admin\", \"domain1\", \"data1\", \"write\"},\n\t}, [][]string{\n\t\t{\"alice\", \"admin\", \"domain1\"},\n\t}, []string{\"domain1\"})\n\n\ttestDeleteDomains(t, []string{}, [][]string{}, [][]string{}, []string{})\n}\n\n// TestGetRolesForUserInDomainWithConditionalFunctions.\nfunc TestGetRolesForUserInDomainWithConditionalFunctions(t *testing.T) {\n\tmodelText := \"examples/rbac_with_domains_conditional_model.conf\"\n\tpolicyText := \"examples/rbac_with_domains_conditional_policy.csv\"\n\n\te, err := NewEnforcer(modelText, policyText)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t}\n\n\t// Test without conditional functions\n\tt.Run(\"WithoutConditionalFunctions\", func(t *testing.T) {\n\t\troles := e.GetRolesForUserInDomain(\"alice\", \"domain1\")\n\t\texpected := []string{\"test1\"}\n\t\tif !util.SetEquals(roles, expected) {\n\t\t\tt.Errorf(\"Expected roles %v, got %v\", expected, roles)\n\t\t}\n\t})\n\n\tt.Run(\"WithConditionalFunctions\", func(t *testing.T) {\n\t\te2, err := NewEnforcer(modelText, policyText)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t\t}\n\n\t\t// Add time-based conditional functions\n\t\tg, err := e2.GetNamedGroupingPolicy(\"g\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to get grouping policy: %v\", err)\n\t\t}\n\t\tfor _, gp := range g {\n\t\t\tif len(gp) >= 4 {\n\t\t\t\te2.AddNamedDomainLinkConditionFunc(\"g\", gp[0], gp[1], gp[2], util.TimeMatchFunc)\n\t\t\t\te2.SetNamedDomainLinkConditionFuncParams(\"g\", gp[0], gp[1], gp[2], \"_\", gp[4])\n\t\t\t}\n\t\t}\n\n\t\troles := e2.GetRolesForUserInDomain(\"alice\", \"domain1\")\n\t\tif roles == nil {\n\t\t\tt.Error(\"GetRolesForUserInDomain should not return nil, even with conditional functions\")\n\t\t}\n\n\t\troles = e2.GetRolesForUserInDomain(\"bob\", \"domain2\")\n\t\tif roles == nil {\n\t\t\tt.Error(\"GetRolesForUserInDomain should not return nil for bob, even with conditional functions\")\n\t\t}\n\t})\n\n\tt.Run(\"WithAlwaysTrueCondition\", func(t *testing.T) {\n\t\te3, err := NewEnforcer(modelText, policyText)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create enforcer: %v\", err)\n\t\t}\n\n\t\t// Use always-true condition function\n\t\talwaysTrueFunc := func(params ...string) (bool, error) {\n\t\t\treturn true, nil\n\t\t}\n\n\t\tg, err := e3.GetNamedGroupingPolicy(\"g\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to get grouping policy: %v\", err)\n\t\t}\n\t\tfor _, gp := range g {\n\t\t\tif len(gp) >= 4 {\n\t\t\t\te3.AddNamedDomainLinkConditionFunc(\"g\", gp[0], gp[1], gp[2], alwaysTrueFunc)\n\t\t\t}\n\t\t}\n\n\t\troles := e3.GetRolesForUserInDomain(\"alice\", \"domain1\")\n\t\texpected := []string{\"test1\"}\n\t\tif !util.SetEquals(roles, expected) {\n\t\t\tt.Errorf(\"Expected roles %v, got %v\", expected, roles)\n\t\t}\n\n\t\troles = e3.GetRolesForUserInDomain(\"bob\", \"domain2\")\n\t\texpected = []string{\"qa1\"}\n\t\tif !util.SetEquals(roles, expected) {\n\t\t\tt.Errorf(\"Expected roles %v, got %v\", expected, roles)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "role_manager_b_test.go",
    "content": "package casbin\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/util\"\n)\n\nfunc BenchmarkRoleManagerSmall(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\t// Do not rebuild the role inheritance relations for every AddGroupingPolicy() call.\n\te.EnableAutoBuildRoleLinks(false)\n\n\t// 100 roles, 10 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 100; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 1000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\trm := e.GetRoleManager()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 0; j < 100; j++ {\n\t\t\t_, _ = rm.HasLink(\"user501\", fmt.Sprintf(\"group%d\", j))\n\t\t}\n\t}\n}\n\nfunc BenchmarkRoleManagerMedium(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\t// Do not rebuild the role inheritance relations for every AddGroupingPolicy() call.\n\te.EnableAutoBuildRoleLinks(false)\n\n\t// 1000 roles, 100 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 1000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 10000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\terr = e.BuildRoleLinks()\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\trm := e.GetRoleManager()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 0; j < 1000; j++ {\n\t\t\t_, _ = rm.HasLink(\"user501\", fmt.Sprintf(\"group%d\", j))\n\t\t}\n\t}\n}\n\nfunc BenchmarkRoleManagerLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\")\n\n\t// 10000 roles, 1000 resources.\n\tpPolicies := make([][]string, 0)\n\tfor i := 0; i < 10000; i++ {\n\t\tpPolicies = append(pPolicies, []string{fmt.Sprintf(\"group%d\", i), fmt.Sprintf(\"data%d\", i/10), \"read\"})\n\t}\n\n\t_, err := e.AddPolicies(pPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\t// 100000 users.\n\tgPolicies := make([][]string, 0)\n\tfor i := 0; i < 100000; i++ {\n\t\tgPolicies = append(gPolicies, []string{fmt.Sprintf(\"user%d\", i), fmt.Sprintf(\"group%d\", i/10)})\n\t}\n\n\t_, err = e.AddGroupingPolicies(gPolicies)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\trm := e.GetRoleManager()\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tfor j := 0; j < 10000; j++ {\n\t\t\t_, _ = rm.HasLink(\"user501\", fmt.Sprintf(\"group%d\", j))\n\t\t}\n\t}\n}\n\nfunc BenchmarkBuildRoleLinksWithPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = e.BuildRoleLinks()\n\t}\n}\n\nfunc BenchmarkBuildRoleLinksWithDomainPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = e.BuildRoleLinks()\n\t}\n}\n\nfunc BenchmarkBuildRoleLinksWithPatternAndDomainPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\te.AddNamedDomainMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = e.BuildRoleLinks()\n\t}\n}\n\nfunc BenchmarkHasLinkWithPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\trm := e.rmMap[\"g\"]\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = rm.HasLink(\"staffUser1001\", \"staff001\", \"/orgs/1/sites/site001\")\n\t}\n}\n\nfunc BenchmarkHasLinkWithDomainPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedDomainMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\trm := e.rmMap[\"g\"]\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = rm.HasLink(\"staffUser1001\", \"staff001\", \"/orgs/1/sites/site001\")\n\t}\n}\n\nfunc BenchmarkHasLinkWithPatternAndDomainPatternLarge(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/performance/rbac_with_pattern_large_scale_model.conf\", \"examples/performance/rbac_with_pattern_large_scale_policy.csv\")\n\te.AddNamedMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\te.AddNamedDomainMatchingFunc(\"g\", \"\", util.KeyMatch4)\n\trm := e.rmMap[\"g\"]\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = rm.HasLink(\"staffUser1001\", \"staff001\", \"/orgs/1/sites/site001\")\n\t}\n}\n\n// BenchmarkConcurrentHasLinkWithMatching benchmarks concurrent HasLink performance with matching functions.\n// Performance test for concurrent access with temporary role creation.\nfunc BenchmarkConcurrentHasLinkWithMatching(b *testing.B) {\n\te, _ := NewEnforcer(\"examples/rbac_with_pattern_model.conf\", \"examples/rbac_with_pattern_policy.csv\")\n\te.AddNamedMatchingFunc(\"g2\", \"keyMatch2\", util.KeyMatch2)\n\trm := e.GetRoleManager()\n\n\tb.ResetTimer()\n\tb.RunParallel(func(pb *testing.PB) {\n\t\tfor pb.Next() {\n\t\t\t_, _ = rm.HasLink(\"alice\", \"/book/123\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "syntax_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\n// TestSyntaxMatcher tests that patterns like \"p.\" inside quoted strings\n// are not incorrectly escaped by EscapeAssertion.\n// This addresses the bug where matchers containing strings like \"a.p.p.l.e\"\n// would have the \".p.\" pattern incorrectly replaced with \"_p_\".\nfunc TestSyntaxMatcher(t *testing.T) {\n\te, err := NewEnforcer(\"examples/syntax_matcher_model.conf\")\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\\n\", err)\n\t}\n\t// The matcher in syntax_matcher_model.conf is: m = r.sub == \"a.p.p.l.e\"\n\t// This should match when r.sub is exactly \"a.p.p.l.e\"\n\ttestEnforce(t, e, \"a.p.p.l.e\", \"file\", \"read\", true)\n\ttestEnforce(t, e, \"other\", \"file\", \"read\", false)\n}\n"
  },
  {
    "path": "transaction.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\").\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software.\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\nconst (\n\t// Default timeout duration for lock acquisition.\n\tdefaultLockTimeout = 30 * time.Second\n)\n\n// Transaction represents a Casbin transaction.\n// It provides methods to perform policy operations within a transaction.\n// and commit or rollback all changes atomically.\ntype Transaction struct {\n\tid          string                     // Unique transaction identifier.\n\tenforcer    *TransactionalEnforcer     // Reference to the transactional enforcer.\n\tbuffer      *TransactionBuffer         // Buffer for policy operations.\n\ttxContext   persist.TransactionContext // Database transaction context.\n\tctx         context.Context            // Context for the transaction.\n\tbaseVersion int64                      // Model version at transaction start.\n\tcommitted   bool                       // Whether the transaction has been committed.\n\trolledBack  bool                       // Whether the transaction has been rolled back.\n\tstartTime   time.Time                  // Transaction start timestamp.\n\tmutex       sync.RWMutex               // Protects transaction state.\n}\n\n// AddPolicy adds a policy within the transaction.\n// The policy is buffered and will be applied when the transaction is committed.\nfunc (tx *Transaction) AddPolicy(params ...interface{}) (bool, error) {\n\treturn tx.AddNamedPolicy(\"p\", params...)\n}\n\n// buildRuleFromParams converts parameters to a rule slice.\nfunc (tx *Transaction) buildRuleFromParams(params ...interface{}) []string {\n\tif len(params) == 1 {\n\t\tif strSlice, ok := params[0].([]string); ok {\n\t\t\trule := make([]string, 0, len(strSlice))\n\t\t\trule = append(rule, strSlice...)\n\t\t\treturn rule\n\t\t}\n\t}\n\n\trule := make([]string, 0, len(params))\n\tfor _, param := range params {\n\t\trule = append(rule, param.(string))\n\t}\n\treturn rule\n}\n\n// checkTransactionStatus checks if the transaction is active.\nfunc (tx *Transaction) checkTransactionStatus() error {\n\tif tx.committed || tx.rolledBack {\n\t\treturn errors.New(\"transaction is not active\")\n\t}\n\treturn nil\n}\n\n// AddNamedPolicy adds a named policy within the transaction.\n// The policy is buffered and will be applied when the transaction is committed.\nfunc (tx *Transaction) AddNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\trule := tx.buildRuleFromParams(params...)\n\n\t// Check if policy already exists in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thasPolicy, err := bufferedModel.HasPolicy(\"p\", ptype, rule)\n\tif hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationAdd,\n\t\tSection:    \"p\",\n\t\tPolicyType: ptype,\n\t\tRules:      [][]string{rule},\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// AddPolicies adds multiple policies within the transaction.\nfunc (tx *Transaction) AddPolicies(rules [][]string) (bool, error) {\n\treturn tx.AddNamedPolicies(\"p\", rules)\n}\n\n// AddNamedPolicies adds multiple named policies within the transaction.\nfunc (tx *Transaction) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\tif len(rules) == 0 {\n\t\treturn false, nil\n\t}\n\n\t// Check if any policies already exist in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar validRules [][]string\n\tfor _, rule := range rules {\n\t\thasPolicy, err := bufferedModel.HasPolicy(\"p\", ptype, rule)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif !hasPolicy {\n\t\t\tvalidRules = append(validRules, rule)\n\t\t}\n\t}\n\n\tif len(validRules) == 0 {\n\t\treturn false, nil\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationAdd,\n\t\tSection:    \"p\",\n\t\tPolicyType: ptype,\n\t\tRules:      validRules,\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// RemovePolicy removes a policy within the transaction.\nfunc (tx *Transaction) RemovePolicy(params ...interface{}) (bool, error) {\n\treturn tx.RemoveNamedPolicy(\"p\", params...)\n}\n\n// RemoveNamedPolicy removes a named policy within the transaction.\nfunc (tx *Transaction) RemoveNamedPolicy(ptype string, params ...interface{}) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\trule := tx.buildRuleFromParams(params...)\n\n\t// Check if policy exists in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thasPolicy, err := bufferedModel.HasPolicy(\"p\", ptype, rule)\n\tif !hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationRemove,\n\t\tSection:    \"p\",\n\t\tPolicyType: ptype,\n\t\tRules:      [][]string{rule},\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// RemovePolicies removes multiple policies within the transaction.\nfunc (tx *Transaction) RemovePolicies(rules [][]string) (bool, error) {\n\treturn tx.RemoveNamedPolicies(\"p\", rules)\n}\n\n// RemoveNamedPolicies removes multiple named policies within the transaction.\nfunc (tx *Transaction) RemoveNamedPolicies(ptype string, rules [][]string) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\tif len(rules) == 0 {\n\t\treturn false, nil\n\t}\n\n\t// Check if policies exist in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tvar validRules [][]string\n\tfor _, rule := range rules {\n\t\thasPolicy, err := bufferedModel.HasPolicy(\"p\", ptype, rule)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif hasPolicy {\n\t\t\tvalidRules = append(validRules, rule)\n\t\t}\n\t}\n\n\tif len(validRules) == 0 {\n\t\treturn false, nil\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationRemove,\n\t\tSection:    \"p\",\n\t\tPolicyType: ptype,\n\t\tRules:      validRules,\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// UpdatePolicy updates a policy within the transaction.\nfunc (tx *Transaction) UpdatePolicy(oldPolicy []string, newPolicy []string) (bool, error) {\n\treturn tx.UpdateNamedPolicy(\"p\", oldPolicy, newPolicy)\n}\n\n// UpdateNamedPolicy updates a named policy within the transaction.\nfunc (tx *Transaction) UpdateNamedPolicy(ptype string, oldPolicy []string, newPolicy []string) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Check if old policy exists and new policy doesn't exist.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thasOldPolicy, err := bufferedModel.HasPolicy(\"p\", ptype, oldPolicy)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !hasOldPolicy {\n\t\treturn false, nil\n\t}\n\n\thasNewPolicy, errNew := bufferedModel.HasPolicy(\"p\", ptype, newPolicy)\n\tif errNew != nil {\n\t\treturn false, errNew\n\t}\n\tif hasNewPolicy {\n\t\treturn false, nil\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationUpdate,\n\t\tSection:    \"p\",\n\t\tPolicyType: ptype,\n\t\tRules:      [][]string{newPolicy},\n\t\tOldRules:   [][]string{oldPolicy},\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// AddGroupingPolicy adds a grouping policy within the transaction.\nfunc (tx *Transaction) AddGroupingPolicy(params ...interface{}) (bool, error) {\n\treturn tx.AddNamedGroupingPolicy(\"g\", params...)\n}\n\n// AddNamedGroupingPolicy adds a named grouping policy within the transaction.\nfunc (tx *Transaction) AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\trule := tx.buildRuleFromParams(params...)\n\n\t// Check if grouping policy already exists in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thasPolicy, err := bufferedModel.HasPolicy(\"g\", ptype, rule)\n\tif hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationAdd,\n\t\tSection:    \"g\",\n\t\tPolicyType: ptype,\n\t\tRules:      [][]string{rule},\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// RemoveGroupingPolicy removes a grouping policy within the transaction.\nfunc (tx *Transaction) RemoveGroupingPolicy(params ...interface{}) (bool, error) {\n\treturn tx.RemoveNamedGroupingPolicy(\"g\", params...)\n}\n\n// RemoveNamedGroupingPolicy removes a named grouping policy within the transaction.\nfunc (tx *Transaction) RemoveNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error) {\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn false, err\n\t}\n\n\trule := tx.buildRuleFromParams(params...)\n\n\t// Check if grouping policy exists in the buffered model.\n\tbufferedModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\thasPolicy, err := bufferedModel.HasPolicy(\"g\", ptype, rule)\n\tif !hasPolicy || err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add operation to buffer.\n\top := persist.PolicyOperation{\n\t\tType:       persist.OperationRemove,\n\t\tSection:    \"g\",\n\t\tPolicyType: ptype,\n\t\tRules:      [][]string{rule},\n\t}\n\ttx.buffer.AddOperation(op)\n\n\treturn true, nil\n}\n\n// GetBufferedModel returns the model as it would look after applying all buffered operations.\n// This is useful for preview or validation purposes within the transaction.\nfunc (tx *Transaction) GetBufferedModel() (model.Model, error) {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\n\tif err := tx.checkTransactionStatus(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n}\n\n// HasOperations returns true if the transaction has any buffered operations.\nfunc (tx *Transaction) HasOperations() bool {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\treturn tx.buffer.HasOperations()\n}\n\n// OperationCount returns the number of buffered operations in the transaction.\nfunc (tx *Transaction) OperationCount() int {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\treturn tx.buffer.OperationCount()\n}\n\n// tryLockWithTimeout attempts to acquire the lock within the specified timeout period.\nfunc tryLockWithTimeout(lock *sync.Mutex, startTime time.Time, maxWait time.Duration) bool {\n\t// Calculate remaining wait time based on transaction start time.\n\tremainingTime := maxWait - time.Since(startTime)\n\tif remainingTime <= 0 {\n\t\treturn false\n\t}\n\n\t// Create a context with timeout for lock acquisition.\n\tctx, cancel := context.WithTimeout(context.Background(), remainingTime)\n\tdefer cancel()\n\n\t// Use channel for timeout control.\n\tdone := make(chan bool, 1)\n\tgo func() {\n\t\tlock.Lock()\n\t\tdone <- true\n\t}()\n\n\t// Wait for either lock acquisition or timeout.\n\tselect {\n\tcase <-done:\n\t\treturn true\n\tcase <-ctx.Done():\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "transaction_buffer.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// TransactionBuffer holds all policy changes made within a transaction.\n// It maintains a list of operations and a snapshot of the model state\n// at the beginning of the transaction.\ntype TransactionBuffer struct {\n\toperations    []persist.PolicyOperation // Buffered operations\n\tmodelSnapshot model.Model               // Model state at transaction start\n\tmutex         sync.RWMutex              // Protects concurrent access\n}\n\n// NewTransactionBuffer creates a new transaction buffer with a model snapshot.\n// The snapshot represents the state of the model at the beginning of the transaction.\nfunc NewTransactionBuffer(baseModel model.Model) *TransactionBuffer {\n\treturn &TransactionBuffer{\n\t\toperations:    make([]persist.PolicyOperation, 0),\n\t\tmodelSnapshot: baseModel.Copy(),\n\t}\n}\n\n// AddOperation adds a policy operation to the buffer.\n// This operation will be applied when the transaction is committed.\nfunc (tb *TransactionBuffer) AddOperation(op persist.PolicyOperation) {\n\ttb.mutex.Lock()\n\tdefer tb.mutex.Unlock()\n\ttb.operations = append(tb.operations, op)\n}\n\n// GetOperations returns all buffered operations.\n// Returns a copy to prevent external modifications.\nfunc (tb *TransactionBuffer) GetOperations() []persist.PolicyOperation {\n\ttb.mutex.RLock()\n\tdefer tb.mutex.RUnlock()\n\n\t// Return a copy to prevent external modifications.\n\tresult := make([]persist.PolicyOperation, len(tb.operations))\n\tcopy(result, tb.operations)\n\treturn result\n}\n\n// Clear removes all buffered operations.\n// This is typically called after a successful commit or rollback.\nfunc (tb *TransactionBuffer) Clear() {\n\ttb.mutex.Lock()\n\tdefer tb.mutex.Unlock()\n\ttb.operations = tb.operations[:0]\n}\n\n// GetModelSnapshot returns the model snapshot taken at transaction start.\n// This represents the original state before any transaction operations.\nfunc (tb *TransactionBuffer) GetModelSnapshot() model.Model {\n\ttb.mutex.RLock()\n\tdefer tb.mutex.RUnlock()\n\treturn tb.modelSnapshot.Copy()\n}\n\n// ApplyOperationsToModel applies all buffered operations to a model and returns the result.\n// This simulates what the model would look like after all operations are applied.\n// It's used for validation and preview purposes within the transaction.\nfunc (tb *TransactionBuffer) ApplyOperationsToModel(baseModel model.Model) (model.Model, error) {\n\ttb.mutex.RLock()\n\tdefer tb.mutex.RUnlock()\n\n\tresultModel := baseModel.Copy()\n\n\tfor _, op := range tb.operations {\n\t\tswitch op.Type {\n\t\tcase persist.OperationAdd:\n\t\t\tfor _, rule := range op.Rules {\n\t\t\t\tif err := resultModel.AddPolicy(op.Section, op.PolicyType, rule); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\tcase persist.OperationRemove:\n\t\t\tfor _, rule := range op.Rules {\n\t\t\t\tif _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, rule); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\tcase persist.OperationUpdate:\n\t\t\t// For update operations, remove old rules and add new ones.\n\t\t\tfor i, oldRule := range op.OldRules {\n\t\t\t\tif i < len(op.Rules) {\n\t\t\t\t\tif _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, oldRule); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tif err := resultModel.AddPolicy(op.Section, op.PolicyType, op.Rules[i]); err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn resultModel, nil\n}\n\n// HasOperations returns true if there are any buffered operations.\nfunc (tb *TransactionBuffer) HasOperations() bool {\n\ttb.mutex.RLock()\n\tdefer tb.mutex.RUnlock()\n\treturn len(tb.operations) > 0\n}\n\n// OperationCount returns the number of buffered operations.\nfunc (tb *TransactionBuffer) OperationCount() int {\n\ttb.mutex.RLock()\n\tdefer tb.mutex.RUnlock()\n\treturn len(tb.operations)\n}\n"
  },
  {
    "path": "transaction_commit.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"errors\"\n\t\"sync/atomic\"\n\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// Commit commits the transaction using a two-phase commit protocol.\n// Phase 1: Apply all operations to the database\n// Phase 2: Apply changes to the in-memory model and rebuild role links.\nfunc (tx *Transaction) Commit() error {\n\t// Try to acquire the commit lock with timeout.\n\tif !tryLockWithTimeout(&tx.enforcer.commitLock, tx.startTime, defaultLockTimeout) {\n\t\t_ = tx.txContext.Rollback()\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn errors.New(\"transaction timeout: failed to acquire lock\")\n\t}\n\tdefer tx.enforcer.commitLock.Unlock()\n\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.committed {\n\t\treturn errors.New(\"transaction already committed\")\n\t}\n\tif tx.rolledBack {\n\t\treturn errors.New(\"transaction already rolled back\")\n\t}\n\n\t// First check if model version has changed.\n\tcurrentVersion := atomic.LoadInt64(&tx.enforcer.modelVersion)\n\tif currentVersion != tx.baseVersion {\n\t\t// Model has been modified, need to check for conflicts.\n\t\tdetector := NewConflictDetector(\n\t\t\ttx.buffer.GetModelSnapshot(),\n\t\t\ttx.enforcer.model,\n\t\t\ttx.buffer.GetOperations(),\n\t\t)\n\t\tif err := detector.DetectConflicts(); err != nil {\n\t\t\t_ = tx.txContext.Rollback()\n\t\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// If no operations, just commit the database transaction and clear state.\n\tif !tx.buffer.HasOperations() {\n\t\tif err := tx.txContext.Commit(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttx.committed = true\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn nil\n\t}\n\n\t// Phase 1: Apply all buffered operations to the database\n\tif err := tx.applyOperationsToDatabase(); err != nil {\n\t\t// Rollback database transaction on failure.\n\t\t_ = tx.txContext.Rollback()\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn err\n\t}\n\n\t// Commit database transaction.\n\tif err := tx.txContext.Commit(); err != nil {\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn err\n\t}\n\n\t// Phase 2: Apply changes to the in-memory model\n\tif err := tx.applyOperationsToModel(); err != nil {\n\t\t// At this point, database is committed but model update failed.\n\t\t// This is a critical error that should not happen in normal circumstances.\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn errors.New(\"critical error: database committed but model update failed: \" + err.Error())\n\t}\n\n\t// Increment model version number.\n\tatomic.AddInt64(&tx.enforcer.modelVersion, 1)\n\n\ttx.committed = true\n\ttx.enforcer.activeTransactions.Delete(tx.id)\n\n\treturn nil\n}\n\n// Rollback rolls back the transaction.\n// This will rollback the database transaction and clear the transaction state.\nfunc (tx *Transaction) Rollback() error {\n\t// Try to acquire the commit lock with timeout.\n\tif !tryLockWithTimeout(&tx.enforcer.commitLock, tx.startTime, defaultLockTimeout) {\n\t\ttx.enforcer.activeTransactions.Delete(tx.id)\n\t\treturn errors.New(\"transaction timeout: failed to acquire lock for rollback\")\n\t}\n\tdefer tx.enforcer.commitLock.Unlock()\n\n\ttx.mutex.Lock()\n\tdefer tx.mutex.Unlock()\n\n\tif tx.committed {\n\t\treturn errors.New(\"transaction already committed\")\n\t}\n\tif tx.rolledBack {\n\t\treturn errors.New(\"transaction already rolled back\")\n\t}\n\n\t// Rollback database transaction.\n\tif err := tx.txContext.Rollback(); err != nil {\n\t\treturn err\n\t}\n\n\ttx.rolledBack = true\n\ttx.enforcer.activeTransactions.Delete(tx.id)\n\n\treturn nil\n}\n\n// applyOperationsToDatabase applies all buffered operations to the database.\nfunc (tx *Transaction) applyOperationsToDatabase() error {\n\toperations := tx.buffer.GetOperations()\n\ttxAdapter := tx.txContext.GetAdapter()\n\n\tfor _, op := range operations {\n\t\tswitch op.Type {\n\t\tcase persist.OperationAdd:\n\t\t\tif err := tx.applyAddOperationToDatabase(txAdapter, op); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase persist.OperationRemove:\n\t\t\tif err := tx.applyRemoveOperationToDatabase(txAdapter, op); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase persist.OperationUpdate:\n\t\t\tif err := tx.applyUpdateOperationToDatabase(txAdapter, op); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// applyAddOperationToDatabase applies an add operation to the database.\nfunc (tx *Transaction) applyAddOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {\n\tif batchAdapter, ok := adapter.(persist.BatchAdapter); ok {\n\t\t// Use batch operation if available.\n\t\treturn batchAdapter.AddPolicies(op.Section, op.PolicyType, op.Rules)\n\t} else {\n\t\t// Fall back to individual operations.\n\t\tfor _, rule := range op.Rules {\n\t\t\tif err := adapter.AddPolicy(op.Section, op.PolicyType, rule); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// applyRemoveOperationToDatabase applies a remove operation to the database.\nfunc (tx *Transaction) applyRemoveOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {\n\tif batchAdapter, ok := adapter.(persist.BatchAdapter); ok {\n\t\t// Use batch operation if available.\n\t\treturn batchAdapter.RemovePolicies(op.Section, op.PolicyType, op.Rules)\n\t} else {\n\t\t// Fall back to individual operations.\n\t\tfor _, rule := range op.Rules {\n\t\t\tif err := adapter.RemovePolicy(op.Section, op.PolicyType, rule); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// applyUpdateOperationToDatabase applies an update operation to the database.\nfunc (tx *Transaction) applyUpdateOperationToDatabase(adapter persist.Adapter, op persist.PolicyOperation) error {\n\tif updateAdapter, ok := adapter.(persist.UpdatableAdapter); ok {\n\t\t// Use update operation if available.\n\t\treturn updateAdapter.UpdatePolicies(op.Section, op.PolicyType, op.OldRules, op.Rules)\n\t}\n\n\t// Fall back to remove + add.\n\tfor i, oldRule := range op.OldRules {\n\t\tif err := adapter.RemovePolicy(op.Section, op.PolicyType, oldRule); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := adapter.AddPolicy(op.Section, op.PolicyType, op.Rules[i]); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// applyOperationsToModel applies all buffered operations to the in-memory model.\nfunc (tx *Transaction) applyOperationsToModel() error {\n\t// Create new model with all operations applied.\n\tnewModel, err := tx.buffer.ApplyOperationsToModel(tx.buffer.GetModelSnapshot())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Replace the enforcer's model.\n\ttx.enforcer.model = newModel\n\ttx.enforcer.invalidateMatcherMap()\n\n\t// Rebuild role links if necessary.\n\tif tx.enforcer.autoBuildRoleLinks {\n\t\t// Check if any operations involved grouping policies.\n\t\toperations := tx.buffer.GetOperations()\n\t\tneedRoleRebuild := false\n\n\t\tfor _, op := range operations {\n\t\t\tif op.Section == \"g\" {\n\t\t\t\tneedRoleRebuild = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif needRoleRebuild {\n\t\t\tif err := tx.enforcer.BuildRoleLinks(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// IsCommitted returns true if the transaction has been committed.\nfunc (tx *Transaction) IsCommitted() bool {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\treturn tx.committed\n}\n\n// IsRolledBack returns true if the transaction has been rolled back.\nfunc (tx *Transaction) IsRolledBack() bool {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\treturn tx.rolledBack\n}\n\n// IsActive returns true if the transaction is still active (not committed or rolled back).\nfunc (tx *Transaction) IsActive() bool {\n\ttx.mutex.RLock()\n\tdefer tx.mutex.RUnlock()\n\treturn !tx.committed && !tx.rolledBack\n}\n"
  },
  {
    "path": "transaction_conflict.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// ConflictError represents a transaction conflict error.\ntype ConflictError struct {\n\tOperation persist.PolicyOperation\n\tReason    string\n}\n\nfunc (e *ConflictError) Error() string {\n\treturn fmt.Sprintf(\"transaction conflict: %s for operation %v\", e.Reason, e.Operation)\n}\n\n// ConflictDetector detects conflicts between transaction operations and current model state.\ntype ConflictDetector struct {\n\tbaseModel    model.Model               // Model snapshot when transaction started\n\tcurrentModel model.Model               // Current model state\n\toperations   []persist.PolicyOperation // Operations to be applied\n}\n\n// NewConflictDetector creates a new conflict detector instance.\nfunc NewConflictDetector(baseModel, currentModel model.Model, operations []persist.PolicyOperation) *ConflictDetector {\n\treturn &ConflictDetector{\n\t\tbaseModel:    baseModel,\n\t\tcurrentModel: currentModel,\n\t\toperations:   operations,\n\t}\n}\n\n// DetectConflicts checks for conflicts between the transaction operations and current model state.\n// Returns nil if no conflicts are found, otherwise returns a ConflictError describing the conflict.\nfunc (cd *ConflictDetector) DetectConflicts() error {\n\tfor _, op := range cd.operations {\n\t\tvar err error\n\t\tswitch op.Type {\n\t\tcase persist.OperationAdd:\n\t\t\t// Add operations never conflict\n\t\t\tcontinue\n\n\t\tcase persist.OperationRemove:\n\t\t\terr = cd.detectRemoveConflict(op)\n\n\t\tcase persist.OperationUpdate:\n\t\t\terr = cd.detectUpdateConflict(op)\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// detectRemoveConflict checks for conflicts in remove operations.\nfunc (cd *ConflictDetector) detectRemoveConflict(op persist.PolicyOperation) error {\n\tfor _, rule := range op.Rules {\n\t\t// Check if policy existed in base model\n\t\tbaseHasPolicy, err := cd.baseModel.HasPolicy(op.Section, op.PolicyType, rule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !baseHasPolicy {\n\t\t\tcontinue // Policy didn't exist when transaction started\n\t\t}\n\n\t\t// Check if policy still exists in current model\n\t\tcurrentHasPolicy, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, rule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !currentHasPolicy {\n\t\t\treturn &ConflictError{\n\t\t\t\tOperation: op,\n\t\t\t\tReason:    \"policy has been removed by another transaction\",\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// detectUpdateConflict checks for conflicts in update operations.\nfunc (cd *ConflictDetector) detectUpdateConflict(op persist.PolicyOperation) error {\n\tfor i, oldRule := range op.OldRules {\n\t\tif i >= len(op.Rules) {\n\t\t\tbreak\n\t\t}\n\t\tnewRule := op.Rules[i]\n\n\t\t// Check if old policy still exists\n\t\toldExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, oldRule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !oldExists {\n\t\t\treturn &ConflictError{\n\t\t\t\tOperation: op,\n\t\t\t\tReason:    \"policy to be updated no longer exists\",\n\t\t\t}\n\t\t}\n\n\t\t// Check if new policy already exists\n\t\tnewExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, newRule)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif newExists {\n\t\t\treturn &ConflictError{\n\t\t\t\tOperation: op,\n\t\t\t\tReason:    \"target policy already exists\",\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "transaction_test.go",
    "content": "// Copyright 2025 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"github.com/casbin/casbin/v3/persist\"\n)\n\n// MockTransactionalAdapter implements TransactionalAdapter interface for testing.\ntype MockTransactionalAdapter struct {\n\tEnforcer *Enforcer\n}\n\n// MockTransactionContext implements TransactionContext interface for testing.\ntype MockTransactionContext struct {\n\tadapter    *MockTransactionalAdapter\n\tcommitted  bool\n\trolledBack bool\n}\n\n// NewMockTransactionalAdapter creates a new mock adapter.\nfunc NewMockTransactionalAdapter() *MockTransactionalAdapter {\n\treturn &MockTransactionalAdapter{}\n}\n\n// LoadPolicy implements Adapter interface.\nfunc (a *MockTransactionalAdapter) LoadPolicy(model model.Model) error {\n\treturn nil\n}\n\n// SavePolicy implements Adapter interface.\nfunc (a *MockTransactionalAdapter) SavePolicy(model model.Model) error {\n\treturn nil\n}\n\n// AddPolicy implements Adapter interface.\nfunc (a *MockTransactionalAdapter) AddPolicy(sec string, ptype string, rule []string) error {\n\treturn nil\n}\n\n// RemovePolicy implements Adapter interface.\nfunc (a *MockTransactionalAdapter) RemovePolicy(sec string, ptype string, rule []string) error {\n\treturn nil\n}\n\n// RemoveFilteredPolicy implements Adapter interface.\nfunc (a *MockTransactionalAdapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {\n\treturn nil\n}\n\n// BeginTransaction implements TransactionalAdapter interface.\nfunc (a *MockTransactionalAdapter) BeginTransaction(ctx context.Context) (persist.TransactionContext, error) {\n\treturn &MockTransactionContext{adapter: a}, nil\n}\n\n// Commit implements TransactionContext interface.\nfunc (tx *MockTransactionContext) Commit() error {\n\tif tx.committed || tx.rolledBack {\n\t\treturn errors.New(\"transaction already finished\")\n\t}\n\ttx.committed = true\n\treturn nil\n}\n\n// Rollback implements TransactionContext interface.\nfunc (tx *MockTransactionContext) Rollback() error {\n\tif tx.committed || tx.rolledBack {\n\t\treturn errors.New(\"transaction already finished\")\n\t}\n\ttx.rolledBack = true\n\treturn nil\n}\n\n// GetAdapter implements TransactionContext interface.\nfunc (tx *MockTransactionContext) GetAdapter() persist.Adapter {\n\treturn tx.adapter\n}\n\n// Test basic transaction functionality.\nfunc TestTransactionBasicOperations(t *testing.T) {\n\tadapter := NewMockTransactionalAdapter()\n\te, err := NewTransactionalEnforcer(\"examples/rbac_model.conf\", adapter)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create transactional enforcer: %v\", err)\n\t}\n\tadapter.Enforcer = e.Enforcer\n\n\tctx := context.Background()\n\n\t// Begin transaction.\n\ttx, err := e.BeginTransaction(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to begin transaction: %v\", err)\n\t}\n\n\t// Add policies in transaction.\n\tok, err := tx.AddPolicy(\"alice\", \"data1\", \"read\")\n\tif !ok || err != nil {\n\t\tt.Fatalf(\"Failed to add policy in transaction: %v\", err)\n\t}\n\n\tok, err = tx.AddPolicy(\"bob\", \"data2\", \"write\")\n\tif !ok || err != nil {\n\t\tt.Fatalf(\"Failed to add policy in transaction: %v\", err)\n\t}\n\n\t// Commit transaction.\n\tif err := tx.Commit(); err != nil {\n\t\tt.Fatalf(\"Failed to commit transaction: %v\", err)\n\t}\n\n\t// Verify transaction was committed.\n\tif !tx.IsCommitted() {\n\t\tt.Error(\"Transaction should be committed\")\n\t}\n}\n\n// Test transaction rollback.\nfunc TestTransactionRollback(t *testing.T) {\n\tadapter := NewMockTransactionalAdapter()\n\te, err := NewTransactionalEnforcer(\"examples/rbac_model.conf\", adapter)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create transactional enforcer: %v\", err)\n\t}\n\tadapter.Enforcer = e.Enforcer\n\n\tctx := context.Background()\n\n\t// Begin transaction.\n\ttx, err := e.BeginTransaction(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to begin transaction: %v\", err)\n\t}\n\n\t// Add policy in transaction.\n\tok, err := tx.AddPolicy(\"alice\", \"data1\", \"read\")\n\tif !ok || err != nil {\n\t\tt.Fatalf(\"Failed to add policy in transaction: %v\", err)\n\t}\n\n\t// Rollback transaction.\n\tif err := tx.Rollback(); err != nil {\n\t\tt.Fatalf(\"Failed to rollback transaction: %v\", err)\n\t}\n\n\t// Verify transaction was rolled back.\n\tif !tx.IsRolledBack() {\n\t\tt.Error(\"Transaction should be rolled back\")\n\t}\n}\n\n// Test concurrent transactions.\nfunc TestConcurrentTransactions(t *testing.T) {\n\tadapter := NewMockTransactionalAdapter()\n\te, err := NewTransactionalEnforcer(\"examples/rbac_model.conf\", adapter)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create transactional enforcer: %v\", err)\n\t}\n\tadapter.Enforcer = e.Enforcer\n\n\tctx := context.Background()\n\n\t// Start first transaction\n\ttx1, err := e.BeginTransaction(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to begin transaction 1: %v\", err)\n\t}\n\n\t// Add policy in first transaction\n\tok, err := tx1.AddPolicy(\"alice\", \"data1\", \"read\")\n\tif !ok || err != nil {\n\t\tt.Fatalf(\"Failed to add policy in transaction 1: %v\", err)\n\t}\n\n\t// Start second transaction\n\ttx2, err := e.BeginTransaction(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to begin transaction 2: %v\", err)\n\t}\n\n\t// Add different policy in second transaction\n\tok, err = tx2.AddPolicy(\"bob\", \"data2\", \"write\")\n\tif !ok || err != nil {\n\t\tt.Fatalf(\"Failed to add policy in transaction 2: %v\", err)\n\t}\n\n\t// Commit first transaction\n\tif err := tx1.Commit(); err != nil {\n\t\tt.Fatalf(\"Failed to commit transaction 1: %v\", err)\n\t}\n\n\t// Commit second transaction\n\tif err := tx2.Commit(); err != nil {\n\t\tt.Fatalf(\"Failed to commit transaction 2: %v\", err)\n\t}\n\n\t// Verify transactions were committed\n\tif !tx1.IsCommitted() {\n\t\tt.Error(\"Transaction 1 should be committed\")\n\t}\n\tif !tx2.IsCommitted() {\n\t\tt.Error(\"Transaction 2 should be committed\")\n\t}\n}\n\n// Test transaction conflicts.\nfunc TestTransactionConflicts(t *testing.T) {\n\tadapter := NewMockTransactionalAdapter()\n\te, err := NewTransactionalEnforcer(\"examples/rbac_model.conf\", adapter)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create transactional enforcer: %v\", err)\n\t}\n\tadapter.Enforcer = e.Enforcer\n\n\tctx := context.Background()\n\n\t// Test Case 1: Two transactions commit\n\tt.Run(\"TwoTransactionsCommit\", func(t *testing.T) {\n\t\ttx1, _ := e.BeginTransaction(ctx)\n\t\ttx2, _ := e.BeginTransaction(ctx)\n\n\t\t// Commit both transactions\n\t\tif err := tx1.Commit(); err != nil {\n\t\t\tt.Fatalf(\"Failed to commit tx1: %v\", err)\n\t\t}\n\t\tif err := tx2.Commit(); err != nil {\n\t\t\tt.Fatalf(\"Failed to commit tx2: %v\", err)\n\t\t}\n\n\t\t// Verify both transactions were committed\n\t\tif !tx1.IsCommitted() {\n\t\t\tt.Error(\"Transaction 1 should be committed\")\n\t\t}\n\t\tif !tx2.IsCommitted() {\n\t\t\tt.Error(\"Transaction 2 should be committed\")\n\t\t}\n\t})\n\n\t// Test Case 2: Transaction rollback\n\tt.Run(\"TransactionRollback\", func(t *testing.T) {\n\t\ttx, _ := e.BeginTransaction(ctx)\n\n\t\t// Rollback transaction\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tt.Fatalf(\"Failed to rollback transaction: %v\", err)\n\t\t}\n\n\t\t// Verify transaction was rolled back\n\t\tif !tx.IsRolledBack() {\n\t\t\tt.Error(\"Transaction should be rolled back\")\n\t\t}\n\t})\n\n\t// Test Case 3: Cannot commit after rollback\n\tt.Run(\"NoCommitAfterRollback\", func(t *testing.T) {\n\t\ttx, _ := e.BeginTransaction(ctx)\n\n\t\t// Rollback transaction\n\t\tif err := tx.Rollback(); err != nil {\n\t\t\tt.Fatalf(\"Failed to rollback transaction: %v\", err)\n\t\t}\n\n\t\t// Try to commit\n\t\tif err := tx.Commit(); err == nil {\n\t\t\tt.Error(\"Should not be able to commit after rollback\")\n\t\t}\n\t})\n}\n\n// Test transaction buffer operations.\nfunc TestTransactionBuffer(t *testing.T) {\n\tadapter := NewMockTransactionalAdapter()\n\te, err := NewTransactionalEnforcer(\"examples/rbac_model.conf\", adapter)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create transactional enforcer: %v\", err)\n\t}\n\tadapter.Enforcer = e.Enforcer\n\n\tctx := context.Background()\n\n\ttx, err := e.BeginTransaction(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to begin transaction: %v\", err)\n\t}\n\n\t// Initially no operations.\n\tif tx.HasOperations() {\n\t\tt.Fatal(\"Transaction should have no operations initially\")\n\t}\n\n\tif tx.OperationCount() != 0 {\n\t\tt.Fatal(\"Operation count should be 0 initially\")\n\t}\n\n\t// Add some operations.\n\ttx.AddPolicy(\"alice\", \"data1\", \"read\")\n\ttx.AddPolicy(\"bob\", \"data2\", \"write\")\n\n\tif !tx.HasOperations() {\n\t\tt.Fatal(\"Transaction should have operations\")\n\t}\n\n\tif tx.OperationCount() != 2 {\n\t\tt.Fatalf(\"Expected 2 operations, got %d\", tx.OperationCount())\n\t}\n\n\t// Get buffered model.\n\tbufferedModel, err := tx.GetBufferedModel()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to get buffered model: %v\", err)\n\t}\n\n\t// Check that buffered model contains the policies.\n\thasPolicy, _ := bufferedModel.HasPolicy(\"p\", \"p\", []string{\"alice\", \"data1\", \"read\"})\n\tif !hasPolicy {\n\t\tt.Fatal(\"Buffered model should contain the added policy\")\n\t}\n\n\ttx.Rollback()\n}\n"
  },
  {
    "path": "util/builtin_operators.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/bmatcuk/doublestar/v4\"\n\n\t\"github.com/casbin/casbin/v3/rbac\"\n\n\t\"github.com/casbin/govaluate\"\n)\n\nvar (\n\tkeyMatch2Re = regexp.MustCompile(`:[^/]+`)\n\tkeyMatch3Re = regexp.MustCompile(`\\{[^/]+\\}`)\n\tkeyMatch4Re = regexp.MustCompile(`{([^/]+)}`)\n\tkeyMatch5Re = regexp.MustCompile(`\\{[^/]+\\}`)\n\tkeyGet2Re1  = regexp.MustCompile(`:[^/]+`)\n\tkeyGet3Re1  = regexp.MustCompile(`\\{[^/]+?\\}`) // non-greedy match of `{...}` to support multiple {} in `/.../`\n\treCache     = map[string]*regexp.Regexp{}\n\treCacheMu   = sync.RWMutex{}\n)\n\nfunc mustCompileOrGet(key string) *regexp.Regexp {\n\treCacheMu.RLock()\n\tre, ok := reCache[key]\n\treCacheMu.RUnlock()\n\n\tif !ok {\n\t\tre = regexp.MustCompile(key)\n\t\treCacheMu.Lock()\n\t\treCache[key] = re\n\t\treCacheMu.Unlock()\n\t}\n\n\treturn re\n}\n\n// validate the variadic parameter size and type as string.\nfunc validateVariadicArgs(expectedLen int, args ...interface{}) error {\n\tif len(args) != expectedLen {\n\t\treturn fmt.Errorf(\"expected %d arguments, but got %d\", expectedLen, len(args))\n\t}\n\n\tfor _, p := range args {\n\t\t_, ok := p.(string)\n\t\tif !ok {\n\t\t\treturn errors.New(\"argument must be a string\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// validate the variadic string parameter size.\nfunc validateVariadicStringArgs(expectedLen int, args ...string) error {\n\tif len(args) != expectedLen {\n\t\treturn fmt.Errorf(\"expected %d arguments, but got %d\", expectedLen, len(args))\n\t}\n\treturn nil\n}\n\n// KeyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.\n// For example, \"/foo/bar\" matches \"/foo/*\".\nfunc KeyMatch(key1 string, key2 string) bool {\n\ti := strings.Index(key2, \"*\")\n\tif i == -1 {\n\t\treturn key1 == key2\n\t}\n\n\tif len(key1) > i {\n\t\treturn key1[:i] == key2[:i]\n\t}\n\treturn key1 == key2[:i]\n}\n\n// KeyMatchFunc is the wrapper for KeyMatch.\nfunc KeyMatchFunc(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyMatch\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyMatch(name1, name2), nil\n}\n\n// KeyGet returns the matched part\n// For example, \"/foo/bar/foo\" matches \"/foo/*\"\n// \"bar/foo\" will been returned.\nfunc KeyGet(key1, key2 string) string {\n\ti := strings.Index(key2, \"*\")\n\tif i == -1 {\n\t\treturn \"\"\n\t}\n\tif len(key1) > i {\n\t\tif key1[:i] == key2[:i] {\n\t\t\treturn key1[i:]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// KeyGetFunc is the wrapper for KeyGet.\nfunc KeyGetFunc(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyGet\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyGet(name1, name2), nil\n}\n\n// KeyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.\n// For example, \"/foo/bar\" matches \"/foo/*\", \"/resource1\" matches \"/:resource\".\nfunc KeyMatch2(key1 string, key2 string) bool {\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\n\tkey2 = keyMatch2Re.ReplaceAllString(key2, \"$1[^/]+$2\")\n\n\treturn RegexMatch(key1, \"^\"+key2+\"$\")\n}\n\n// KeyMatch2Func is the wrapper for KeyMatch2.\nfunc KeyMatch2Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyMatch2\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyMatch2(name1, name2), nil\n}\n\n// KeyGet2 returns value matched pattern\n// For example, \"/resource1\" matches \"/:resource\"\n// if the pathVar == \"resource\", then \"resource1\" will be returned.\nfunc KeyGet2(key1, key2 string, pathVar string) string {\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\tkeys := keyGet2Re1.FindAllString(key2, -1)\n\tkey2 = keyGet2Re1.ReplaceAllString(key2, \"$1([^/]+)$2\")\n\tkey2 = \"^\" + key2 + \"$\"\n\n\tre := mustCompileOrGet(key2)\n\tvalues := re.FindAllStringSubmatch(key1, -1)\n\tif len(values) == 0 {\n\t\treturn \"\"\n\t}\n\tfor i, key := range keys {\n\t\tif pathVar == key[1:] {\n\t\t\treturn values[0][i+1]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// KeyGet2Func is the wrapper for KeyGet2.\nfunc KeyGet2Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(3, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyGet2\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\tkey := args[2].(string)\n\n\treturn KeyGet2(name1, name2, key), nil\n}\n\n// KeyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.\n// For example, \"/foo/bar\" matches \"/foo/*\", \"/resource1\" matches \"/{resource}\".\nfunc KeyMatch3(key1 string, key2 string) bool {\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\tkey2 = keyMatch3Re.ReplaceAllString(key2, \"$1[^/]+$2\")\n\n\treturn RegexMatch(key1, \"^\"+key2+\"$\")\n}\n\n// KeyMatch3Func is the wrapper for KeyMatch3.\nfunc KeyMatch3Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyMatch3\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyMatch3(name1, name2), nil\n}\n\n// KeyGet3 returns value matched pattern\n// For example, \"project/proj_project1_admin/\" matches \"project/proj_{project}_admin/\"\n// if the pathVar == \"project\", then \"project1\" will be returned.\nfunc KeyGet3(key1, key2 string, pathVar string) string {\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\n\tkeys := keyGet3Re1.FindAllString(key2, -1)\n\tkey2 = keyGet3Re1.ReplaceAllString(key2, \"$1([^/]+?)$2\")\n\tkey2 = \"^\" + key2 + \"$\"\n\tre := mustCompileOrGet(key2)\n\tvalues := re.FindAllStringSubmatch(key1, -1)\n\tif len(values) == 0 {\n\t\treturn \"\"\n\t}\n\tfor i, key := range keys {\n\t\tif pathVar == key[1:len(key)-1] {\n\t\t\treturn values[0][i+1]\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// KeyGet3Func is the wrapper for KeyGet3.\nfunc KeyGet3Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(3, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyGet3\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\tkey := args[2].(string)\n\n\treturn KeyGet3(name1, name2, key), nil\n}\n\n// KeyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.\n// Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns:\n// \"/parent/123/child/123\" matches \"/parent/{id}/child/{id}\"\n// \"/parent/123/child/456\" does not match \"/parent/{id}/child/{id}\"\n// But KeyMatch3 will match both.\nfunc KeyMatch4(key1 string, key2 string) bool {\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\n\ttokens := []string{}\n\n\tre := keyMatch4Re\n\tkey2 = re.ReplaceAllStringFunc(key2, func(s string) string {\n\t\ttokens = append(tokens, s[1:len(s)-1])\n\t\treturn \"([^/]+)\"\n\t})\n\n\tre = mustCompileOrGet(\"^\" + key2 + \"$\")\n\tmatches := re.FindStringSubmatch(key1)\n\tif matches == nil {\n\t\treturn false\n\t}\n\tmatches = matches[1:]\n\n\tif len(tokens) != len(matches) {\n\t\tpanic(errors.New(\"KeyMatch4: number of tokens is not equal to number of values\"))\n\t}\n\n\tvalues := map[string]string{}\n\n\tfor key, token := range tokens {\n\t\tif _, ok := values[token]; !ok {\n\t\t\tvalues[token] = matches[key]\n\t\t}\n\t\tif values[token] != matches[key] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// KeyMatch4Func is the wrapper for KeyMatch4.\nfunc KeyMatch4Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyMatch4\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyMatch4(name1, name2), nil\n}\n\n// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *\n// For example,\n// - \"/foo/bar?status=1&type=2\" matches \"/foo/bar\"\n// - \"/parent/child1\" and \"/parent/child1\" matches \"/parent/*\"\n// - \"/parent/child1?status=1\" matches \"/parent/*\".\nfunc KeyMatch5(key1 string, key2 string) bool {\n\ti := strings.Index(key1, \"?\")\n\n\tif i != -1 {\n\t\tkey1 = key1[:i]\n\t}\n\n\tkey2 = strings.Replace(key2, \"/*\", \"/.*\", -1)\n\tkey2 = keyMatch5Re.ReplaceAllString(key2, \"$1[^/]+$2\")\n\n\treturn RegexMatch(key1, \"^\"+key2+\"$\")\n}\n\n// KeyMatch5Func is the wrapper for KeyMatch5.\nfunc KeyMatch5Func(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"keyMatch5\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn KeyMatch5(name1, name2), nil\n}\n\n// RegexMatch determines whether key1 matches the pattern of key2 in regular expression.\nfunc RegexMatch(key1 string, key2 string) bool {\n\tres, err := regexp.MatchString(key2, key1)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn res\n}\n\n// RegexMatchFunc is the wrapper for RegexMatch.\nfunc RegexMatchFunc(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"regexMatch\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn RegexMatch(name1, name2), nil\n}\n\n// IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern.\n// For example, \"192.168.2.123\" matches \"192.168.2.0/24\".\nfunc IPMatch(ip1 string, ip2 string) bool {\n\tobjIP1 := net.ParseIP(ip1)\n\tif objIP1 == nil {\n\t\tpanic(\"invalid argument: ip1 in IPMatch() function is not an IP address.\")\n\t}\n\n\t_, cidr, err := net.ParseCIDR(ip2)\n\tif err != nil {\n\t\tobjIP2 := net.ParseIP(ip2)\n\t\tif objIP2 == nil {\n\t\t\tpanic(\"invalid argument: ip2 in IPMatch() function is neither an IP address nor a CIDR.\")\n\t\t}\n\n\t\treturn objIP1.Equal(objIP2)\n\t}\n\n\treturn cidr.Contains(objIP1)\n}\n\n// IPMatchFunc is the wrapper for IPMatch.\nfunc IPMatchFunc(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"ipMatch\", err)\n\t}\n\n\tip1 := args[0].(string)\n\tip2 := args[1].(string)\n\n\treturn IPMatch(ip1, ip2), nil\n}\n\n// GlobMatch determines whether key1 matches the pattern of key2 using glob pattern.\nfunc GlobMatch(key1 string, key2 string) (bool, error) {\n\treturn doublestar.Match(key2, key1)\n}\n\n// GlobMatchFunc is the wrapper for GlobMatch.\nfunc GlobMatchFunc(args ...interface{}) (interface{}, error) {\n\tif err := validateVariadicArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"globMatch\", err)\n\t}\n\n\tname1 := args[0].(string)\n\tname2 := args[1].(string)\n\n\treturn GlobMatch(name1, name2)\n}\n\n// GenerateGFunction is the factory method of the g(_, _[, _]) function.\nfunc GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {\n\tmemorized := sync.Map{}\n\treturn func(args ...interface{}) (interface{}, error) {\n\t\t// Like all our other govaluate functions, all args are strings.\n\n\t\t// Allocate and generate a cache key from the arguments...\n\t\ttotal := len(args)\n\t\tfor _, a := range args {\n\t\t\taStr := a.(string)\n\t\t\ttotal += len(aStr)\n\t\t}\n\t\tbuilder := strings.Builder{}\n\t\tbuilder.Grow(total)\n\t\tfor _, arg := range args {\n\t\t\tbuilder.WriteByte(0)\n\t\t\tbuilder.WriteString(arg.(string))\n\t\t}\n\t\tkey := builder.String()\n\n\t\t// ...and see if we've already calculated this.\n\t\tv, found := memorized.Load(key)\n\t\tif found {\n\t\t\treturn v, nil\n\t\t}\n\n\t\t// If not, do the calculation.\n\t\t// There are guaranteed to be exactly 2 or 3 arguments.\n\t\tname1, name2 := args[0].(string), args[1].(string)\n\t\tif rm == nil {\n\t\t\tv = name1 == name2\n\t\t} else if len(args) == 2 {\n\t\t\tv, _ = rm.HasLink(name1, name2)\n\t\t} else {\n\t\t\tdomain := args[2].(string)\n\t\t\tv, _ = rm.HasLink(name1, name2, domain)\n\t\t}\n\n\t\tmemorized.Store(key, v)\n\t\treturn v, nil\n\t}\n}\n\n// GenerateConditionalGFunction is the factory method of the g(_, _[, _]) function with conditions.\nfunc GenerateConditionalGFunction(crm rbac.ConditionalRoleManager) govaluate.ExpressionFunction {\n\treturn func(args ...interface{}) (interface{}, error) {\n\t\t// Like all our other govaluate functions, all args are strings.\n\t\tvar hasLink bool\n\n\t\tname1, name2 := args[0].(string), args[1].(string)\n\t\tif crm == nil {\n\t\t\thasLink = name1 == name2\n\t\t} else if len(args) == 2 {\n\t\t\thasLink, _ = crm.HasLink(name1, name2)\n\t\t} else {\n\t\t\tdomain := args[2].(string)\n\t\t\thasLink, _ = crm.HasLink(name1, name2, domain)\n\t\t}\n\n\t\treturn hasLink, nil\n\t}\n}\n\n// builtin LinkConditionFunc\n\n// TimeMatchFunc is the wrapper for TimeMatch.\nfunc TimeMatchFunc(args ...string) (bool, error) {\n\tif err := validateVariadicStringArgs(2, args...); err != nil {\n\t\treturn false, fmt.Errorf(\"%s: %w\", \"TimeMatch\", err)\n\t}\n\treturn TimeMatch(args[0], args[1])\n}\n\n// TimeMatch determines whether the current time is between startTime and endTime.\n// You can use \"_\" to indicate that the parameter is ignored.\nfunc TimeMatch(startTime, endTime string) (bool, error) {\n\tnow := time.Now()\n\tif startTime != \"_\" {\n\t\tif start, err := time.Parse(\"2006-01-02 15:04:05\", startTime); err != nil {\n\t\t\treturn false, err\n\t\t} else if !now.After(start) {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\tif endTime != \"_\" {\n\t\tif end, err := time.Parse(\"2006-01-02 15:04:05\", endTime); err != nil {\n\t\t\treturn false, err\n\t\t} else if !now.Before(end) {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "util/builtin_operators_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\nimport (\n\t\"testing\"\n)\n\nfunc testKeyMatch(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes := KeyMatch(key1, key2)\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc TestKeyMatch(t *testing.T) {\n\ttestKeyMatch(t, \"/foo\", \"/foo\", true)\n\ttestKeyMatch(t, \"/foo\", \"/foo*\", true)\n\ttestKeyMatch(t, \"/foo\", \"/foo/*\", false)\n\ttestKeyMatch(t, \"/foo/bar\", \"/foo\", false)\n\ttestKeyMatch(t, \"/foo/bar\", \"/foo*\", true)\n\ttestKeyMatch(t, \"/foo/bar\", \"/foo/*\", true)\n\ttestKeyMatch(t, \"/foobar\", \"/foo\", false)\n\ttestKeyMatch(t, \"/foobar\", \"/foo*\", true)\n\ttestKeyMatch(t, \"/foobar\", \"/foo/*\", false)\n}\n\nfunc testKeyGet(t *testing.T, key1 string, key2 string, res string) {\n\tt.Helper()\n\tmyRes := KeyGet(key1, key2)\n\tt.Logf(`%s < %s: \"%s\"`, key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(`%s < %s: \"%s\", supposed to be \"%s\"`, key1, key2, myRes, res)\n\t}\n}\n\nfunc TestKeyGet(t *testing.T) {\n\ttestKeyGet(t, \"/foo\", \"/foo\", \"\")\n\ttestKeyGet(t, \"/foo\", \"/foo*\", \"\")\n\ttestKeyGet(t, \"/foo\", \"/foo/*\", \"\")\n\ttestKeyGet(t, \"/foo/bar\", \"/foo\", \"\")\n\ttestKeyGet(t, \"/foo/bar\", \"/foo*\", \"/bar\")\n\ttestKeyGet(t, \"/foo/bar\", \"/foo/*\", \"bar\")\n\ttestKeyGet(t, \"/foobar\", \"/foo\", \"\")\n\ttestKeyGet(t, \"/foobar\", \"/foo*\", \"bar\")\n\ttestKeyGet(t, \"/foobar\", \"/foo/*\", \"\")\n}\n\nfunc testKeyMatch2(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes := KeyMatch2(key1, key2)\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc testGlobMatch(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes, err := GlobMatch(key1, key2)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc TestKeyMatch2(t *testing.T) {\n\ttestKeyMatch2(t, \"/foo\", \"/foo\", true)\n\ttestKeyMatch2(t, \"/foo\", \"/foo*\", true)\n\ttestKeyMatch2(t, \"/foo\", \"/foo/*\", false)\n\ttestKeyMatch2(t, \"/foo/bar\", \"/foo\", false)\n\ttestKeyMatch2(t, \"/foo/bar\", \"/foo*\", false) // different with KeyMatch.\n\ttestKeyMatch2(t, \"/foo/bar\", \"/foo/*\", true)\n\ttestKeyMatch2(t, \"/foobar\", \"/foo\", false)\n\ttestKeyMatch2(t, \"/foobar\", \"/foo*\", false) // different with KeyMatch.\n\ttestKeyMatch2(t, \"/foobar\", \"/foo/*\", false)\n\n\ttestKeyMatch2(t, \"/\", \"/:resource\", false)\n\ttestKeyMatch2(t, \"/resource1\", \"/:resource\", true)\n\ttestKeyMatch2(t, \"/myid\", \"/:id/using/:resId\", false)\n\ttestKeyMatch2(t, \"/myid/using/myresid\", \"/:id/using/:resId\", true)\n\n\ttestKeyMatch2(t, \"/proxy/myid\", \"/proxy/:id/*\", false)\n\ttestKeyMatch2(t, \"/proxy/myid/\", \"/proxy/:id/*\", true)\n\ttestKeyMatch2(t, \"/proxy/myid/res\", \"/proxy/:id/*\", true)\n\ttestKeyMatch2(t, \"/proxy/myid/res/res2\", \"/proxy/:id/*\", true)\n\ttestKeyMatch2(t, \"/proxy/myid/res/res2/res3\", \"/proxy/:id/*\", true)\n\ttestKeyMatch2(t, \"/proxy/\", \"/proxy/:id/*\", false)\n\n\ttestKeyMatch2(t, \"/alice\", \"/:id\", true)\n\ttestKeyMatch2(t, \"/alice/all\", \"/:id/all\", true)\n\ttestKeyMatch2(t, \"/alice\", \"/:id/all\", false)\n\ttestKeyMatch2(t, \"/alice/all\", \"/:id\", false)\n\n\ttestKeyMatch2(t, \"/alice/all\", \"/:/all\", false)\n}\n\nfunc testKeyGet2(t *testing.T, key1 string, key2 string, pathVar string, res string) {\n\tt.Helper()\n\tmyRes := KeyGet2(key1, key2, pathVar)\n\tt.Logf(`%s < %s: %s = \"%s\"`, key1, key2, pathVar, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(`%s < %s: %s = \"%s\" supposed to be \"%s\"`, key1, key2, pathVar, myRes, res)\n\t}\n}\n\nfunc TestKeyGet2(t *testing.T) {\n\ttestKeyGet2(t, \"/foo\", \"/foo\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foo\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foo\", \"/foo/*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foo/bar\", \"/foo\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foo/bar\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foo/bar\", \"/foo/*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foobar\", \"/foo\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foobar\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/foobar\", \"/foo/*\", \"id\", \"\")\n\n\ttestKeyGet2(t, \"/\", \"/:resource\", \"resource\", \"\")\n\ttestKeyGet2(t, \"/resource1\", \"/:resource\", \"resource\", \"resource1\")\n\ttestKeyGet2(t, \"/myid\", \"/:id/using/:resId\", \"id\", \"\")\n\ttestKeyGet2(t, \"/myid/using/myresid\", \"/:id/using/:resId\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/myid/using/myresid\", \"/:id/using/:resId\", \"resId\", \"myresid\")\n\n\ttestKeyGet2(t, \"/proxy/myid\", \"/proxy/:id/*\", \"id\", \"\")\n\ttestKeyGet2(t, \"/proxy/myid/\", \"/proxy/:id/*\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/proxy/myid/res\", \"/proxy/:id/*\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/proxy/myid/res/res2\", \"/proxy/:id/*\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/proxy/myid/res/res2/res3\", \"/proxy/:id/*\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/proxy/myid/res/res2/res3\", \"/proxy/:id/res/*\", \"id\", \"myid\")\n\ttestKeyGet2(t, \"/proxy/\", \"/proxy/:id/*\", \"id\", \"\")\n\n\ttestKeyGet2(t, \"/alice\", \"/:id\", \"id\", \"alice\")\n\ttestKeyGet2(t, \"/alice/all\", \"/:id/all\", \"id\", \"alice\")\n\ttestKeyGet2(t, \"/alice\", \"/:id/all\", \"id\", \"\")\n\ttestKeyGet2(t, \"/alice/all\", \"/:id\", \"id\", \"\")\n\n\ttestKeyGet2(t, \"/alice/all\", \"/:/all\", \"\", \"\")\n}\n\nfunc testKeyMatch3(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes := KeyMatch3(key1, key2)\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc TestKeyMatch3(t *testing.T) {\n\t// keyMatch3() is similar with KeyMatch2(), except using \"/proxy/{id}\" instead of \"/proxy/:id\".\n\ttestKeyMatch3(t, \"/foo\", \"/foo\", true)\n\ttestKeyMatch3(t, \"/foo\", \"/foo*\", true)\n\ttestKeyMatch3(t, \"/foo\", \"/foo/*\", false)\n\ttestKeyMatch3(t, \"/foo/bar\", \"/foo\", false)\n\ttestKeyMatch3(t, \"/foo/bar\", \"/foo*\", false)\n\ttestKeyMatch3(t, \"/foo/bar\", \"/foo/*\", true)\n\ttestKeyMatch3(t, \"/foobar\", \"/foo\", false)\n\ttestKeyMatch3(t, \"/foobar\", \"/foo*\", false)\n\ttestKeyMatch3(t, \"/foobar\", \"/foo/*\", false)\n\n\ttestKeyMatch3(t, \"/\", \"/{resource}\", false)\n\ttestKeyMatch3(t, \"/resource1\", \"/{resource}\", true)\n\ttestKeyMatch3(t, \"/myid\", \"/{id}/using/{resId}\", false)\n\ttestKeyMatch3(t, \"/myid/using/myresid\", \"/{id}/using/{resId}\", true)\n\n\ttestKeyMatch3(t, \"/proxy/myid\", \"/proxy/{id}/*\", false)\n\ttestKeyMatch3(t, \"/proxy/myid/\", \"/proxy/{id}/*\", true)\n\ttestKeyMatch3(t, \"/proxy/myid/res\", \"/proxy/{id}/*\", true)\n\ttestKeyMatch3(t, \"/proxy/myid/res/res2\", \"/proxy/{id}/*\", true)\n\ttestKeyMatch3(t, \"/proxy/myid/res/res2/res3\", \"/proxy/{id}/*\", true)\n\ttestKeyMatch3(t, \"/proxy/\", \"/proxy/{id}/*\", false)\n\n\ttestKeyMatch3(t, \"/myid/using/myresid\", \"/{id/using/{resId}\", false)\n}\n\nfunc testKeyGet3(t *testing.T, key1 string, key2 string, pathVar string, res string) {\n\tt.Helper()\n\tmyRes := KeyGet3(key1, key2, pathVar)\n\tt.Logf(`%s < %s: %s = \"%s\"`, key1, key2, pathVar, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(`%s < %s: %s = \"%s\" supposed to be \"%s\"`, key1, key2, pathVar, myRes, res)\n\t}\n}\n\nfunc TestKeyGet3(t *testing.T) {\n\t// KeyGet3() is similar with KeyGet2(), except using \"/proxy/{id}\" instead of \"/proxy/:id\".\n\ttestKeyGet3(t, \"/foo\", \"/foo\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foo\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foo\", \"/foo/*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foo/bar\", \"/foo\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foo/bar\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foo/bar\", \"/foo/*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foobar\", \"/foo\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foobar\", \"/foo*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/foobar\", \"/foo/*\", \"id\", \"\")\n\n\ttestKeyGet3(t, \"/\", \"/{resource}\", \"resource\", \"\")\n\ttestKeyGet3(t, \"/resource1\", \"/{resource}\", \"resource\", \"resource1\")\n\ttestKeyGet3(t, \"/myid\", \"/{id}/using/{resId}\", \"id\", \"\")\n\ttestKeyGet3(t, \"/myid/using/myresid\", \"/{id}/using/{resId}\", \"id\", \"myid\")\n\ttestKeyGet3(t, \"/myid/using/myresid\", \"/{id}/using/{resId}\", \"resId\", \"myresid\")\n\n\ttestKeyGet3(t, \"/proxy/myid\", \"/proxy/{id}/*\", \"id\", \"\")\n\ttestKeyGet3(t, \"/proxy/myid/\", \"/proxy/{id}/*\", \"id\", \"myid\")\n\ttestKeyGet3(t, \"/proxy/myid/res\", \"/proxy/{id}/*\", \"id\", \"myid\")\n\ttestKeyGet3(t, \"/proxy/myid/res/res2\", \"/proxy/{id}/*\", \"id\", \"myid\")\n\ttestKeyGet3(t, \"/proxy/myid/res/res2/res3\", \"/proxy/{id}/*\", \"id\", \"myid\")\n\ttestKeyGet3(t, \"/proxy/\", \"/proxy/{id}/*\", \"id\", \"\")\n\n\ttestKeyGet3(t, \"/api/group1_group_name/project1_admin/info\", \"/api/{proj}_admin/info\",\n\t\t\"proj\", \"\")\n\ttestKeyGet3(t, \"/{id/using/myresid\", \"/{id/using/{resId}\", \"resId\", \"myresid\")\n\ttestKeyGet3(t, \"/{id/using/myresid/status}\", \"/{id/using/{resId}/status}\", \"resId\", \"myresid\")\n\n\ttestKeyGet3(t, \"/proxy/myid/res/res2/res3\", \"/proxy/{id}/*/{res}\", \"res\", \"res3\")\n\ttestKeyGet3(t, \"/api/project1_admin/info\", \"/api/{proj}_admin/info\", \"proj\", \"project1\")\n\ttestKeyGet3(t, \"/api/group1_group_name/project1_admin/info\", \"/api/{g}_{gn}/{proj}_admin/info\",\n\t\t\"g\", \"group1\")\n\ttestKeyGet3(t, \"/api/group1_group_name/project1_admin/info\", \"/api/{g}_{gn}/{proj}_admin/info\",\n\t\t\"gn\", \"group_name\")\n\ttestKeyGet3(t, \"/api/group1_group_name/project1_admin/info\", \"/api/{g}_{gn}/{proj}_admin/info\",\n\t\t\"proj\", \"project1\")\n}\n\nfunc testKeyMatch4(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes := KeyMatch4(key1, key2)\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc TestKeyMatch4(t *testing.T) {\n\ttestKeyMatch4(t, \"/parent/123/child/123\", \"/parent/{id}/child/{id}\", true)\n\ttestKeyMatch4(t, \"/parent/123/child/456\", \"/parent/{id}/child/{id}\", false)\n\n\ttestKeyMatch4(t, \"/parent/123/child/123\", \"/parent/{id}/child/{another_id}\", true)\n\ttestKeyMatch4(t, \"/parent/123/child/456\", \"/parent/{id}/child/{another_id}\", true)\n\n\ttestKeyMatch4(t, \"/parent/123/child/123/book/123\", \"/parent/{id}/child/{id}/book/{id}\", true)\n\ttestKeyMatch4(t, \"/parent/123/child/123/book/456\", \"/parent/{id}/child/{id}/book/{id}\", false)\n\ttestKeyMatch4(t, \"/parent/123/child/456/book/123\", \"/parent/{id}/child/{id}/book/{id}\", false)\n\ttestKeyMatch4(t, \"/parent/123/child/456/book/\", \"/parent/{id}/child/{id}/book/{id}\", false)\n\ttestKeyMatch4(t, \"/parent/123/child/456\", \"/parent/{id}/child/{id}/book/{id}\", false)\n\n\ttestKeyMatch4(t, \"/parent/123/child/123\", \"/parent/{i/d}/child/{i/d}\", false)\n}\n\nfunc testRegexMatch(t *testing.T, key1 string, key2 string, res bool) {\n\tt.Helper()\n\tmyRes := RegexMatch(key1, key2)\n\tt.Logf(\"%s < %s: %t\", key1, key2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", key1, key2, !res, res)\n\t}\n}\n\nfunc TestRegexMatch(t *testing.T) {\n\ttestRegexMatch(t, \"/topic/create\", \"/topic/create\", true)\n\ttestRegexMatch(t, \"/topic/create/123\", \"/topic/create\", true)\n\ttestRegexMatch(t, \"/topic/delete\", \"/topic/create\", false)\n\ttestRegexMatch(t, \"/topic/edit\", \"/topic/edit/[0-9]+\", false)\n\ttestRegexMatch(t, \"/topic/edit/123\", \"/topic/edit/[0-9]+\", true)\n\ttestRegexMatch(t, \"/topic/edit/abc\", \"/topic/edit/[0-9]+\", false)\n\ttestRegexMatch(t, \"/foo/delete/123\", \"/topic/delete/[0-9]+\", false)\n\ttestRegexMatch(t, \"/topic/delete/0\", \"/topic/delete/[0-9]+\", true)\n\ttestRegexMatch(t, \"/topic/edit/123s\", \"/topic/delete/[0-9]+\", false)\n}\n\nfunc testIPMatch(t *testing.T, ip1 string, ip2 string, res bool) {\n\tt.Helper()\n\tmyRes := IPMatch(ip1, ip2)\n\tt.Logf(\"%s < %s: %t\", ip1, ip2, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", ip1, ip2, !res, res)\n\t}\n}\n\nfunc TestIPMatch(t *testing.T) {\n\ttestIPMatch(t, \"192.168.2.123\", \"192.168.2.0/24\", true)\n\ttestIPMatch(t, \"192.168.2.123\", \"192.168.3.0/24\", false)\n\ttestIPMatch(t, \"192.168.2.123\", \"192.168.2.0/16\", true)\n\ttestIPMatch(t, \"192.168.2.123\", \"192.168.2.123\", true)\n\ttestIPMatch(t, \"192.168.2.123\", \"192.168.2.123/32\", true)\n\ttestIPMatch(t, \"10.0.0.11\", \"10.0.0.0/8\", true)\n\ttestIPMatch(t, \"11.0.0.123\", \"10.0.0.0/8\", false)\n}\n\nfunc testRegexMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := RegexMatchFunc(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testKeyMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := KeyMatchFunc(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testKeyMatch2Func(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := KeyMatch2Func(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testKeyMatch3Func(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := KeyMatch3Func(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testKeyMatch4Func(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := KeyMatch4Func(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testKeyMatch5Func(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := KeyMatch5Func(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc testIPMatchFunc(t *testing.T, res bool, err string, args ...interface{}) {\n\tt.Helper()\n\tmyRes, myErr := IPMatchFunc(args...)\n\tmyErrStr := \"\"\n\n\tif myErr != nil {\n\t\tmyErrStr = myErr.Error()\n\t}\n\n\tif myRes != res || err != myErrStr {\n\t\tt.Errorf(\"%v returns %v %v, supposed to be %v %v\", args, myRes, myErr, res, err)\n\t}\n}\n\nfunc TestRegexMatchFunc(t *testing.T) {\n\ttestRegexMatchFunc(t, false, \"regexMatch: expected 2 arguments, but got 1\", \"/topic/create\")\n\ttestRegexMatchFunc(t, false, \"regexMatch: expected 2 arguments, but got 3\", \"/topic/create/123\", \"/topic/create\", \"/topic/update\")\n\ttestRegexMatchFunc(t, false, \"regexMatch: argument must be a string\", \"/topic/create\", false)\n\ttestRegexMatchFunc(t, true, \"\", \"/topic/create/123\", \"/topic/create\")\n}\n\nfunc TestKeyMatchFunc(t *testing.T) {\n\ttestKeyMatchFunc(t, false, \"keyMatch: expected 2 arguments, but got 1\", \"/foo\")\n\ttestKeyMatchFunc(t, false, \"keyMatch: expected 2 arguments, but got 3\", \"/foo/create/123\", \"/foo/*\", \"/foo/update/123\")\n\ttestKeyMatchFunc(t, false, \"keyMatch: argument must be a string\", \"/foo\", true)\n\ttestKeyMatchFunc(t, false, \"\", \"/foo/bar\", \"/foo\")\n\ttestKeyMatchFunc(t, true, \"\", \"/foo/bar\", \"/foo/*\")\n\ttestKeyMatchFunc(t, true, \"\", \"/foo/bar\", \"/foo*\")\n}\n\nfunc TestKeyMatch2Func(t *testing.T) {\n\ttestKeyMatch2Func(t, false, \"keyMatch2: expected 2 arguments, but got 1\", \"/\")\n\ttestKeyMatch2Func(t, false, \"keyMatch2: expected 2 arguments, but got 3\", \"/foo/create/123\", \"/*\", \"/foo/update/123\")\n\ttestKeyMatch2Func(t, false, \"keyMatch2: argument must be a string\", \"/foo\", true)\n\n\ttestKeyMatch2Func(t, false, \"\", \"/\", \"/:resource\")\n\ttestKeyMatch2Func(t, true, \"\", \"/resource1\", \"/:resource\")\n\n\ttestKeyMatch2Func(t, true, \"\", \"/foo\", \"/foo\")\n\ttestKeyMatch2Func(t, true, \"\", \"/foo\", \"/foo*\")\n\ttestKeyMatch2Func(t, false, \"\", \"/foo\", \"/foo/*\")\n}\n\nfunc TestKeyMatch3Func(t *testing.T) {\n\ttestKeyMatch3Func(t, false, \"keyMatch3: expected 2 arguments, but got 1\", \"/\")\n\ttestKeyMatch3Func(t, false, \"keyMatch3: expected 2 arguments, but got 3\", \"/foo/create/123\", \"/*\", \"/foo/update/123\")\n\ttestKeyMatch3Func(t, false, \"keyMatch3: argument must be a string\", \"/foo\", true)\n\n\ttestKeyMatch3Func(t, true, \"\", \"/foo\", \"/foo\")\n\ttestKeyMatch3Func(t, true, \"\", \"/foo\", \"/foo*\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foo\", \"/foo/*\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foo/bar\", \"/foo\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foo/bar\", \"/foo*\")\n\ttestKeyMatch3Func(t, true, \"\", \"/foo/bar\", \"/foo/*\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foobar\", \"/foo\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foobar\", \"/foo*\")\n\ttestKeyMatch3Func(t, false, \"\", \"/foobar\", \"/foo/*\")\n\n\ttestKeyMatch3Func(t, false, \"\", \"/\", \"/{resource}\")\n\ttestKeyMatch3Func(t, true, \"\", \"/resource1\", \"/{resource}\")\n\ttestKeyMatch3Func(t, false, \"\", \"/myid\", \"/{id}/using/{resId}\")\n\ttestKeyMatch3Func(t, true, \"\", \"/myid/using/myresid\", \"/{id}/using/{resId}\")\n\n\ttestKeyMatch3Func(t, false, \"\", \"/proxy/myid\", \"/proxy/{id}/*\")\n\ttestKeyMatch3Func(t, true, \"\", \"/proxy/myid/\", \"/proxy/{id}/*\")\n\ttestKeyMatch3Func(t, true, \"\", \"/proxy/myid/res\", \"/proxy/{id}/*\")\n\ttestKeyMatch3Func(t, true, \"\", \"/proxy/myid/res/res2\", \"/proxy/{id}/*\")\n\ttestKeyMatch3Func(t, true, \"\", \"/proxy/myid/res/res2/res3\", \"/proxy/{id}/*\")\n\ttestKeyMatch3Func(t, false, \"\", \"/proxy/\", \"/proxy/{id}/*\")\n}\n\nfunc TestKeyMatch4Func(t *testing.T) {\n\ttestKeyMatch4Func(t, false, \"keyMatch4: expected 2 arguments, but got 1\", \"/parent/123/child/123\")\n\ttestKeyMatch4Func(t, false, \"keyMatch4: expected 2 arguments, but got 3\", \"/parent/123/child/123\", \"/parent/{id}/child/{id}\", true)\n\ttestKeyMatch4Func(t, false, \"keyMatch4: argument must be a string\", \"/parent/123/child/123\", true)\n\n\ttestKeyMatch4Func(t, true, \"\", \"/parent/123/child/123\", \"/parent/{id}/child/{id}\")\n\ttestKeyMatch4Func(t, false, \"\", \"/parent/123/child/456\", \"/parent/{id}/child/{id}\")\n\n\ttestKeyMatch4Func(t, true, \"\", \"/parent/123/child/123\", \"/parent/{id}/child/{another_id}\")\n\ttestKeyMatch4Func(t, true, \"\", \"/parent/123/child/456\", \"/parent/{id}/child/{another_id}\")\n}\n\nfunc TestKeyMatch5Func(t *testing.T) {\n\ttestKeyMatch5Func(t, false, \"keyMatch5: expected 2 arguments, but got 1\", \"/foo\")\n\ttestKeyMatch5Func(t, false, \"keyMatch5: expected 2 arguments, but got 3\", \"/foo/create/123\", \"/foo/*\", \"/foo/update/123\")\n\ttestKeyMatch5Func(t, false, \"keyMatch5: argument must be a string\", \"/parent/123\", true)\n\n\ttestKeyMatch5Func(t, true, \"\", \"/parent/child?status=1&type=2\", \"/parent/child\")\n\ttestKeyMatch5Func(t, false, \"\", \"/parent?status=1&type=2\", \"/parent/child\")\n\n\ttestKeyMatch5Func(t, true, \"\", \"/parent/child/?status=1&type=2\", \"/parent/child/\")\n\ttestKeyMatch5Func(t, false, \"\", \"/parent/child/?status=1&type=2\", \"/parent/child\")\n\ttestKeyMatch5Func(t, false, \"\", \"/parent/child?status=1&type=2\", \"/parent/child/\")\n\n\ttestKeyMatch5Func(t, true, \"\", \"/foo\", \"/foo\")\n\ttestKeyMatch5Func(t, true, \"\", \"/foo\", \"/foo*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foo\", \"/foo/*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foo/bar\", \"/foo\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foo/bar\", \"/foo*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/foo/bar\", \"/foo/*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foobar\", \"/foo\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foobar\", \"/foo*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/foobar\", \"/foo/*\")\n\n\ttestKeyMatch5Func(t, false, \"\", \"/\", \"/{resource}\")\n\ttestKeyMatch5Func(t, true, \"\", \"/resource1\", \"/{resource}\")\n\ttestKeyMatch5Func(t, false, \"\", \"/myid\", \"/{id}/using/{resId}\")\n\ttestKeyMatch5Func(t, true, \"\", \"/myid/using/myresid\", \"/{id}/using/{resId}\")\n\n\ttestKeyMatch5Func(t, false, \"\", \"/proxy/myid\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res/res2\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res/res2/res3\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/proxy/\", \"/proxy/{id}/*\")\n\n\ttestKeyMatch5Func(t, false, \"\", \"/proxy/myid?status=1&type=2\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res?status=1&type=2\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res/res2?status=1&type=2\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, true, \"\", \"/proxy/myid/res/res2/res3?status=1&type=2\", \"/proxy/{id}/*\")\n\ttestKeyMatch5Func(t, false, \"\", \"/proxy/\", \"/proxy/{id}/*\")\n}\n\nfunc TestIPMatchFunc(t *testing.T) {\n\ttestIPMatchFunc(t, false, \"ipMatch: expected 2 arguments, but got 1\", \"192.168.2.123\")\n\ttestIPMatchFunc(t, false, \"ipMatch: argument must be a string\", \"192.168.2.123\", 128)\n\ttestIPMatchFunc(t, true, \"\", \"192.168.2.123\", \"192.168.2.0/24\")\n}\n\nfunc TestGlobMatch(t *testing.T) {\n\ttestGlobMatch(t, \"/foo\", \"/foo\", true)\n\ttestGlobMatch(t, \"/foo\", \"/foo*\", true)\n\ttestGlobMatch(t, \"/foo\", \"/foo/*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"/foo\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"/foo*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"/foo/*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"/foo\", false)\n\ttestGlobMatch(t, \"/foobar\", \"/foo*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"/foo/*\", false)\n\n\ttestGlobMatch(t, \"/foo\", \"*/foo\", true)\n\ttestGlobMatch(t, \"/foo\", \"*/foo*\", true)\n\ttestGlobMatch(t, \"/foo\", \"*/foo/*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"*/foo/*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/foobar\", \"*/foo*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"*/foo/*\", false)\n\n\ttestGlobMatch(t, \"/prefix/foo\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/foo\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/foo\", \"*/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"*/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"*/foo/*\", false)\n\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"*/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"*/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"*/foo\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"*/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"*/foo/*\", false)\n\n\ttestGlobMatch(t, \"/foo\", \"**/foo\", true)\n\ttestGlobMatch(t, \"/foo\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/foo\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/foo/bar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"**/foo**\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/foobar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/foobar\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/foobar\", \"**/foo/**\", false)\n\n\ttestGlobMatch(t, \"/prefix/foo\", \"**/foo\", true)\n\ttestGlobMatch(t, \"/prefix/foo\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/prefix/foo\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"**/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"**/foo/**\", false)\n\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"**/foo\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"**/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"**/foo/**\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"**/foo\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"**/foo**\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"**/foo/**\", false)\n\n\ttestGlobMatch(t, \"/foo\", \"*/foo**\", true)\n\ttestGlobMatch(t, \"/foo\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/foo\", \"*/foo/**\", true)\n\ttestGlobMatch(t, \"/foo\", \"**/foo/*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"**/foo*\", false)\n\ttestGlobMatch(t, \"/foo/bar\", \"*/foo/**\", true)\n\ttestGlobMatch(t, \"/foo/bar\", \"**/foo/*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"*/foo**\", true)\n\ttestGlobMatch(t, \"/foobar\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/foobar\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/foobar\", \"**/foo/*\", false)\n\n\ttestGlobMatch(t, \"/prefix/foo\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/foo\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/prefix/foo\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/foo\", \"**/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"**/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/foo/bar\", \"**/foo/*\", true)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/foobar\", \"**/foo/*\", false)\n\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo\", \"**/foo/*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"**/foo*\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foo/bar\", \"**/foo/*\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"*/foo**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"**/foo*\", true)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"*/foo/**\", false)\n\ttestGlobMatch(t, \"/prefix/subprefix/foobar\", \"**/foo/*\", false)\n}\n\nfunc testTimeMatch(t *testing.T, startTime string, endTime string, res bool) {\n\tt.Helper()\n\tmyRes, err := TimeMatch(startTime, endTime)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tt.Logf(\"%s < %s: %t\", startTime, endTime, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s < %s: %t, supposed to be %t\", startTime, endTime, !res, res)\n\t}\n}\n\nfunc TestTestMatch(t *testing.T) {\n\ttestTimeMatch(t, \"0000-01-01 00:00:00\", \"0000-01-02 00:00:00\", false)\n\ttestTimeMatch(t, \"0000-01-01 00:00:00\", \"9999-12-30 00:00:00\", true)\n\ttestTimeMatch(t, \"_\", \"_\", true)\n\ttestTimeMatch(t, \"_\", \"9999-12-30 00:00:00\", true)\n\ttestTimeMatch(t, \"_\", \"0000-01-02 00:00:00\", false)\n\ttestTimeMatch(t, \"0000-01-01 00:00:00\", \"_\", true)\n\ttestTimeMatch(t, \"9999-12-30 00:00:00\", \"_\", false)\n}\n"
  },
  {
    "path": "util/util.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\nimport (\n\t\"encoding/json\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar evalReg = regexp.MustCompile(`\\beval\\((?P<rule>[^)]*)\\)`)\n\nvar escapeAssertionRegex = regexp.MustCompile(`([()\\s|&,=!><+\\-*/]|^)((r|p)[0-9]*)\\.`)\n\nfunc JsonToMap(jsonStr string) (map[string]interface{}, error) {\n\tresult := make(map[string]interface{})\n\terr := json.Unmarshal([]byte(jsonStr), &result)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\treturn result, nil\n}\n\n// EscapeAssertion escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.\nfunc EscapeAssertion(s string) string {\n\ts = escapeAssertionRegex.ReplaceAllStringFunc(s, func(m string) string {\n\t\t// Replace only the last dot with underscore (preserve the prefix character)\n\t\tlastDotIdx := strings.LastIndex(m, \".\")\n\t\tif lastDotIdx > 0 {\n\t\t\treturn m[:lastDotIdx] + \"_\"\n\t\t}\n\t\treturn m\n\t})\n\treturn s\n}\n\n// RemoveComments removes the comments starting with # in the text.\nfunc RemoveComments(s string) string {\n\tpos := strings.Index(s, \"#\")\n\tif pos == -1 {\n\t\treturn s\n\t}\n\treturn strings.TrimSpace(s[0:pos])\n}\n\n// ArrayEquals determines whether two string arrays are identical.\nfunc ArrayEquals(a []string, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Array2DEquals determines whether two 2-dimensional string arrays are identical.\nfunc Array2DEquals(a [][]string, b [][]string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tfor i, v := range a {\n\t\tif !ArrayEquals(v, b[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// SortArray2D  Sorts the two-dimensional string array.\nfunc SortArray2D(arr [][]string) {\n\tif len(arr) == 0 {\n\t\treturn\n\t}\n\tsort.Slice(arr, func(i, j int) bool {\n\t\tminArrLen := len(arr[i])\n\t\tif len(arr[j]) < minArrLen {\n\t\t\tminArrLen = len(arr[j])\n\t\t}\n\t\tfor k := 0; k < minArrLen; k++ {\n\t\t\tif arr[i][k] != arr[j][k] {\n\t\t\t\treturn arr[i][k] < arr[j][k]\n\t\t\t}\n\t\t}\n\t\treturn len(arr[i]) < len(arr[j])\n\t})\n}\n\n// SortedArray2DEquals determines whether two 2-dimensional string arrays are identical.\nfunc SortedArray2DEquals(a [][]string, b [][]string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tcopyA := make([][]string, len(a))\n\tcopy(copyA, a)\n\tcopyB := make([][]string, len(b))\n\tcopy(copyB, b)\n\n\tSortArray2D(copyA)\n\tSortArray2D(copyB)\n\n\tfor i, v := range copyA {\n\t\tif !ArrayEquals(v, copyB[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// ArrayRemoveDuplicates removes any duplicated elements in a string array.\nfunc ArrayRemoveDuplicates(s *[]string) {\n\tfound := make(map[string]bool)\n\tj := 0\n\tfor i, x := range *s {\n\t\tif !found[x] {\n\t\t\tfound[x] = true\n\t\t\t(*s)[j] = (*s)[i]\n\t\t\tj++\n\t\t}\n\t}\n\t*s = (*s)[:j]\n}\n\n// ArrayToString gets a printable string for a string array.\nfunc ArrayToString(s []string) string {\n\treturn strings.Join(s, \", \")\n}\n\n// ParamsToString gets a printable string for variable number of parameters.\nfunc ParamsToString(s ...string) string {\n\treturn strings.Join(s, \", \")\n}\n\n// SetEquals determines whether two string sets are identical.\nfunc SetEquals(a []string, b []string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tsort.Strings(a)\n\tsort.Strings(b)\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// SetEquals determines whether two int sets are identical.\nfunc SetEqualsInt(a []int, b []int) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tsort.Ints(a)\n\tsort.Ints(b)\n\n\tfor i, v := range a {\n\t\tif v != b[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Set2DEquals determines whether two string slice sets are identical.\nfunc Set2DEquals(a [][]string, b [][]string) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\n\tvar aa []string\n\tfor _, v := range a {\n\t\tsort.Strings(v)\n\t\taa = append(aa, strings.Join(v, \", \"))\n\t}\n\tvar bb []string\n\tfor _, v := range b {\n\t\tsort.Strings(v)\n\t\tbb = append(bb, strings.Join(v, \", \"))\n\t}\n\n\treturn SetEquals(aa, bb)\n}\n\n// JoinSlice joins a string and a slice into a new slice.\nfunc JoinSlice(a string, b ...string) []string {\n\tres := make([]string, 0, len(b)+1)\n\n\tres = append(res, a)\n\tres = append(res, b...)\n\n\treturn res\n}\n\n// JoinSliceAny joins a string and a slice into a new interface{} slice.\nfunc JoinSliceAny(a string, b ...string) []interface{} {\n\tres := make([]interface{}, 0, len(b)+1)\n\n\tres = append(res, a)\n\tfor _, s := range b {\n\t\tres = append(res, s)\n\t}\n\n\treturn res\n}\n\n// SetSubtract returns the elements in `a` that aren't in `b`.\nfunc SetSubtract(a []string, b []string) []string {\n\tmb := make(map[string]struct{}, len(b))\n\tfor _, x := range b {\n\t\tmb[x] = struct{}{}\n\t}\n\tvar diff []string\n\tfor _, x := range a {\n\t\tif _, found := mb[x]; !found {\n\t\t\tdiff = append(diff, x)\n\t\t}\n\t}\n\treturn diff\n}\n\n// HasEval determine whether matcher contains function eval.\nfunc HasEval(s string) bool {\n\treturn evalReg.MatchString(s)\n}\n\n// ReplaceEval replace function eval with the value of its parameters.\nfunc ReplaceEval(s string, rule string) string {\n\treturn evalReg.ReplaceAllString(s, \"(\"+rule+\")\")\n}\n\n// ReplaceEvalWithMap replace function eval with the value of its parameters via given sets.\nfunc ReplaceEvalWithMap(src string, sets map[string]string) string {\n\treturn evalReg.ReplaceAllStringFunc(src, func(s string) string {\n\t\tsubs := evalReg.FindStringSubmatch(s)\n\t\tif subs == nil {\n\t\t\treturn s\n\t\t}\n\t\tkey := subs[1]\n\t\tvalue, found := sets[key]\n\t\tif !found {\n\t\t\treturn s\n\t\t}\n\t\treturn evalReg.ReplaceAllString(s, value)\n\t})\n}\n\n// GetEvalValue returns the parameters of function eval.\nfunc GetEvalValue(s string) []string {\n\tsubMatch := evalReg.FindAllStringSubmatch(s, -1)\n\tvar rules []string\n\tfor _, rule := range subMatch {\n\t\trules = append(rules, rule[1])\n\t}\n\treturn rules\n}\n\n// EscapeStringLiterals escapes backslashes in string literals within an expression\n// to ensure consistent handling between govaluate (which interprets escape sequences)\n// and CSV parsing (which treats backslashes as literal characters).\n// This function doubles all backslashes within single-quoted and double-quoted strings.\nfunc EscapeStringLiterals(expr string) string {\n\tvar result strings.Builder\n\tinString := false\n\tvar quote rune\n\n\tfor i := 0; i < len(expr); i++ {\n\t\tch := rune(expr[i])\n\n\t\tif inString {\n\t\t\tresult.WriteRune(ch)\n\t\t\tif ch == '\\\\' {\n\t\t\t\t// Found a backslash inside a string - double it\n\t\t\t\tresult.WriteRune('\\\\')\n\t\t\t} else if ch == quote {\n\t\t\t\t// End of string literal\n\t\t\t\tinString = false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Not inside a string literal\n\t\tif ch == '\\'' || ch == '\"' {\n\t\t\tinString = true\n\t\t\tquote = ch\n\t\t}\n\t\tresult.WriteRune(ch)\n\t}\n\n\treturn result.String()\n}\n\nfunc RemoveDuplicateElement(s []string) []string {\n\tresult := make([]string, 0, len(s))\n\ttemp := map[string]struct{}{}\n\tfor _, item := range s {\n\t\tif _, ok := temp[item]; !ok {\n\t\t\ttemp[item] = struct{}{}\n\t\t\tresult = append(result, item)\n\t\t}\n\t}\n\treturn result\n}\n\ntype node struct {\n\tkey   interface{}\n\tvalue interface{}\n\tprev  *node\n\tnext  *node\n}\n\ntype LRUCache struct {\n\tcapacity int\n\tm        map[interface{}]*node\n\thead     *node\n\ttail     *node\n}\n\nfunc NewLRUCache(capacity int) *LRUCache {\n\tcache := &LRUCache{}\n\tcache.capacity = capacity\n\tcache.m = map[interface{}]*node{}\n\n\thead := &node{}\n\ttail := &node{}\n\n\thead.next = tail\n\ttail.prev = head\n\n\tcache.head = head\n\tcache.tail = tail\n\n\treturn cache\n}\n\nfunc (cache *LRUCache) remove(n *node, listOnly bool) {\n\tif !listOnly {\n\t\tdelete(cache.m, n.key)\n\t}\n\tn.prev.next = n.next\n\tn.next.prev = n.prev\n}\n\nfunc (cache *LRUCache) add(n *node, listOnly bool) {\n\tif !listOnly {\n\t\tcache.m[n.key] = n\n\t}\n\theadNext := cache.head.next\n\tcache.head.next = n\n\theadNext.prev = n\n\tn.next = headNext\n\tn.prev = cache.head\n}\n\nfunc (cache *LRUCache) moveToHead(n *node) {\n\tcache.remove(n, true)\n\tcache.add(n, true)\n}\n\nfunc (cache *LRUCache) Get(key interface{}) (value interface{}, ok bool) {\n\tn, ok := cache.m[key]\n\tif ok {\n\t\tcache.moveToHead(n)\n\t\treturn n.value, ok\n\t} else {\n\t\treturn nil, ok\n\t}\n}\n\nfunc (cache *LRUCache) Put(key interface{}, value interface{}) {\n\tn, ok := cache.m[key]\n\tif ok {\n\t\tcache.remove(n, false)\n\t} else {\n\t\tn = &node{key, value, nil, nil}\n\t\tif len(cache.m) >= cache.capacity {\n\t\t\tcache.remove(cache.tail.prev, false)\n\t\t}\n\t}\n\tcache.add(n, false)\n}\n\ntype SyncLRUCache struct {\n\trwm sync.RWMutex\n\t*LRUCache\n}\n\nfunc NewSyncLRUCache(capacity int) *SyncLRUCache {\n\tcache := &SyncLRUCache{}\n\tcache.LRUCache = NewLRUCache(capacity)\n\treturn cache\n}\n\nfunc (cache *SyncLRUCache) Get(key interface{}) (value interface{}, ok bool) {\n\tcache.rwm.Lock()\n\tdefer cache.rwm.Unlock()\n\treturn cache.LRUCache.Get(key)\n}\n\nfunc (cache *SyncLRUCache) Put(key interface{}, value interface{}) {\n\tcache.rwm.Lock()\n\tdefer cache.rwm.Unlock()\n\tcache.LRUCache.Put(key, value)\n}\n"
  },
  {
    "path": "util/util_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage util\n\nimport (\n\t\"testing\"\n)\n\nfunc testEscapeAssertion(t *testing.T, s string, res string) {\n\tt.Helper()\n\tmyRes := EscapeAssertion(s)\n\tt.Logf(\"%s: %s\", s, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s: %s, supposed to be %s\", s, myRes, res)\n\t}\n}\n\nfunc TestEscapeAssertion(t *testing.T) {\n\ttestEscapeAssertion(t, \"r_sub == r_obj.value\", \"r_sub == r_obj.value\")\n\ttestEscapeAssertion(t, \"p_sub == r_sub.value\", \"p_sub == r_sub.value\")\n\ttestEscapeAssertion(t, \"r.attr.value == p.attr\", \"r_attr.value == p_attr\")\n\ttestEscapeAssertion(t, \"r.attr.value == p.attr\", \"r_attr.value == p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value || p.attr\", \"r_attp.value || p_attr\")\n\ttestEscapeAssertion(t, \"r2.attr.value == p2.attr\", \"r2_attr.value == p2_attr\")\n\ttestEscapeAssertion(t, \"r2.attp.value || p2.attr\", \"r2_attp.value || p2_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value &&p.attr\", \"r_attp.value &&p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value >p.attr\", \"r_attp.value >p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value <p.attr\", \"r_attp.value <p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value +p.attr\", \"r_attp.value +p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value -p.attr\", \"r_attp.value -p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value *p.attr\", \"r_attp.value *p_attr\")\n\ttestEscapeAssertion(t, \"r.attp.value /p.attr\", \"r_attp.value /p_attr\")\n\ttestEscapeAssertion(t, \"!r.attp.value /p.attr\", \"!r_attp.value /p_attr\")\n\ttestEscapeAssertion(t, \"g(r.sub, p.sub) == p.attr\", \"g(r_sub, p_sub) == p_attr\")\n\ttestEscapeAssertion(t, \"g(r.sub,p.sub) == p.attr\", \"g(r_sub,p_sub) == p_attr\")\n\ttestEscapeAssertion(t, \"(r.attp.value || p.attr)p.u\", \"(r_attp.value || p_attr)p_u\")\n\t// Test that patterns inside strings are not escaped\n\ttestEscapeAssertion(t, `r.sub == \"a.p.p.l.e\"`, `r_sub == \"a.p.p.l.e\"`)\n\ttestEscapeAssertion(t, `r.sub == \"test.p.value\"`, `r_sub == \"test.p.value\"`)\n}\n\nfunc testRemoveComments(t *testing.T, s string, res string) {\n\tt.Helper()\n\tmyRes := RemoveComments(s)\n\tt.Logf(\"%s: %s\", s, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s: %s, supposed to be %s\", s, myRes, res)\n\t}\n}\n\nfunc TestRemoveComments(t *testing.T) {\n\ttestRemoveComments(t, \"r.act == p.act # comments\", \"r.act == p.act\")\n\ttestRemoveComments(t, \"r.act == p.act#comments\", \"r.act == p.act\")\n\ttestRemoveComments(t, \"r.act == p.act###\", \"r.act == p.act\")\n\ttestRemoveComments(t, \"### comments\", \"\")\n\ttestRemoveComments(t, \"r.act == p.act\", \"r.act == p.act\")\n}\n\nfunc testArrayEquals(t *testing.T, a []string, b []string, res bool) {\n\tt.Helper()\n\tmyRes := ArrayEquals(a, b)\n\tt.Logf(\"%s == %s: %t\", a, b, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s == %s: %t, supposed to be %t\", a, b, myRes, res)\n\t}\n}\n\nfunc TestArrayEquals(t *testing.T) {\n\ttestArrayEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"b\", \"c\"}, true)\n\ttestArrayEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"b\"}, false)\n\ttestArrayEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"c\", \"b\"}, false)\n\ttestArrayEquals(t, []string{\"a\", \"b\", \"c\"}, []string{}, false)\n}\n\nfunc testArray2DEquals(t *testing.T, a [][]string, b [][]string, res bool) {\n\tt.Helper()\n\tmyRes := Array2DEquals(a, b)\n\tt.Logf(\"%s == %s: %t\", a, b, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s == %s: %t, supposed to be %t\", a, b, myRes, res)\n\t}\n}\n\nfunc TestArray2DEquals(t *testing.T) {\n\ttestArray2DEquals(t, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, true)\n\ttestArray2DEquals(t, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, [][]string{{\"a\", \"b\", \"c\"}}, false)\n\ttestArray2DEquals(t, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\"}}, false)\n\ttestArray2DEquals(t, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, [][]string{{\"1\", \"2\", \"3\"}, {\"a\", \"b\", \"c\"}}, false)\n\ttestArray2DEquals(t, [][]string{{\"a\", \"b\", \"c\"}, {\"1\", \"2\", \"3\"}}, [][]string{}, false)\n}\n\nfunc testSetEquals(t *testing.T, a []string, b []string, res bool) {\n\tt.Helper()\n\tmyRes := SetEquals(a, b)\n\tt.Logf(\"%s == %s: %t\", a, b, myRes)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s == %s: %t, supposed to be %t\", a, b, myRes, res)\n\t}\n}\n\nfunc TestSetEquals(t *testing.T) {\n\ttestSetEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"b\", \"c\"}, true)\n\ttestSetEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"b\"}, false)\n\ttestSetEquals(t, []string{\"a\", \"b\", \"c\"}, []string{\"a\", \"c\", \"b\"}, true)\n\ttestSetEquals(t, []string{\"a\", \"b\", \"c\"}, []string{}, false)\n}\n\nfunc testContainEval(t *testing.T, s string, res bool) {\n\tt.Helper()\n\tmyRes := HasEval(s)\n\tif myRes != res {\n\t\tt.Errorf(\"%s: %t, supposed to be %t\", s, myRes, res)\n\t}\n}\nfunc TestContainEval(t *testing.T) {\n\ttestContainEval(t, \"eval() && a && b && c\", true)\n\ttestContainEval(t, \"eval) && a && b && c\", false)\n\ttestContainEval(t, \"eval)( && a && b && c\", false)\n\ttestContainEval(t, \"eval(c * (a + b)) && a && b && c\", true)\n\ttestContainEval(t, \"xeval() && a && b && c\", false)\n}\n\nfunc testReplaceEval(t *testing.T, s string, rule string, res string) {\n\tt.Helper()\n\tmyRes := ReplaceEval(s, rule)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s: %s supposed to be %s\", s, myRes, res)\n\t}\n}\nfunc TestReplaceEval(t *testing.T) {\n\ttestReplaceEval(t, \"eval() && a && b && c\", \"a\", \"(a) && a && b && c\")\n\ttestReplaceEval(t, \"eval() && a && b && c\", \"(a)\", \"((a)) && a && b && c\")\n}\n\nfunc testGetEvalValue(t *testing.T, s string, res []string) {\n\tt.Helper()\n\tmyRes := GetEvalValue(s)\n\n\tif !ArrayEquals(myRes, res) {\n\t\tt.Errorf(\"%s: %s supposed to be %s\", s, myRes, res)\n\t}\n}\n\nfunc TestGetEvalValue(t *testing.T) {\n\ttestGetEvalValue(t, \"eval(a) && a && b && c\", []string{\"a\"})\n\ttestGetEvalValue(t, \"a && eval(a) && b && c\", []string{\"a\"})\n\ttestGetEvalValue(t, \"eval(a) && eval(b) && a && b && c\", []string{\"a\", \"b\"})\n\ttestGetEvalValue(t, \"a && eval(a) && eval(b) && b && c\", []string{\"a\", \"b\"})\n}\n\nfunc testReplaceEvalWithMap(t *testing.T, s string, sets map[string]string, res string) {\n\tt.Helper()\n\tmyRes := ReplaceEvalWithMap(s, sets)\n\n\tif myRes != res {\n\t\tt.Errorf(\"%s: %s supposed to be %s\", s, myRes, res)\n\t}\n}\n\nfunc TestReplaceEvalWithMap(t *testing.T) {\n\ttestReplaceEvalWithMap(t, \"eval(rule1)\", map[string]string{\"rule1\": \"a == b\"}, \"a == b\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) && c && d\", map[string]string{\"rule1\": \"a == b\"}, \"a == b && c && d\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1)\", nil, \"eval(rule1)\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) && c && d\", nil, \"eval(rule1) && c && d\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2)\", map[string]string{\"rule1\": \"a == b\", \"rule2\": \"a == c\"}, \"a == b || a == c\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2) && c && d\", map[string]string{\"rule1\": \"a == b\", \"rule2\": \"a == c\"}, \"a == b || a == c && c && d\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2)\", map[string]string{\"rule1\": \"a == b\"}, \"a == b || eval(rule2)\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2) && c && d\", map[string]string{\"rule1\": \"a == b\"}, \"a == b || eval(rule2) && c && d\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2)\", map[string]string{\"rule2\": \"a == b\"}, \"eval(rule1) || a == b\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2) && c && d\", map[string]string{\"rule2\": \"a == b\"}, \"eval(rule1) || a == b && c && d\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2)\", nil, \"eval(rule1) || eval(rule2)\")\n\ttestReplaceEvalWithMap(t, \"eval(rule1) || eval(rule2) && c && d\", nil, \"eval(rule1) || eval(rule2) && c && d\")\n}\n\nfunc testCacheGet(t *testing.T, c *LRUCache, key string, value interface{}, ok bool) {\n\tv, o := c.Get(key)\n\tif v != value || o != ok {\n\t\tt.Errorf(\"Get(%s): (%s, %t) supposed to be (%s, %t)\", key, v, o, value, ok)\n\t}\n}\n\nfunc testCachePut(t *testing.T, c *LRUCache, key string, value interface{}) {\n\tc.Put(key, value)\n\tv, o := c.Get(key)\n\tif v != value || o != true {\n\t\tt.Errorf(\"Put(%s, %s): didn't add value\", key, value)\n\t}\n}\n\nfunc testCacheEqual(t *testing.T, c *LRUCache, values []int) {\n\tcacheValues := []int{}\n\tfor _, v := range c.m {\n\t\tcacheValues = append(cacheValues, v.value.(int))\n\t}\n\n\tif SetEqualsInt(values, cacheValues) == false {\n\t\tt.Errorf(\"cache values: %d supposed to be %d\", cacheValues, values)\n\t}\n}\n\nfunc TestLRUCache(t *testing.T) {\n\tcache := NewLRUCache(3)\n\ttestCachePut(t, cache, \"one\", 1)\n\ttestCachePut(t, cache, \"two\", 2)\n\ttestCacheGet(t, cache, \"one\", 1, true)\n\ttestCachePut(t, cache, \"three\", 3)\n\ttestCachePut(t, cache, \"four\", 4)\n\ttestCacheGet(t, cache, \"two\", nil, false)\n\ttestCacheEqual(t, cache, []int{1, 3, 4})\n}\n\nfunc testEscapeStringLiterals(t *testing.T, input string, expected string) {\n\tt.Helper()\n\tresult := EscapeStringLiterals(input)\n\tt.Logf(\"Input: %q\", input)\n\tt.Logf(\"Expected: %q\", expected)\n\tt.Logf(\"Got: %q\", result)\n\n\tif result != expected {\n\t\tt.Errorf(\"EscapeStringLiterals(%q) = %q, expected %q\", input, result, expected)\n\t}\n}\n\nfunc TestEscapeStringLiterals(t *testing.T) {\n\t// Test single-quoted strings\n\ttestEscapeStringLiterals(t, `'\\1\\2'`, `'\\\\1\\\\2'`)\n\ttestEscapeStringLiterals(t, `'\\n\\t'`, `'\\\\n\\\\t'`)\n\ttestEscapeStringLiterals(t, `'\\\\already\\\\escaped'`, `'\\\\\\\\already\\\\\\\\escaped'`)\n\n\t// Test double-quoted strings\n\ttestEscapeStringLiterals(t, `\"\\1\\2\"`, `\"\\\\1\\\\2\"`)\n\ttestEscapeStringLiterals(t, `\"\\n\\t\"`, `\"\\\\n\\\\t\"`)\n\n\t// Test expressions with string literals\n\ttestEscapeStringLiterals(t, `regexMatch('\\1\\2', p.obj)`, `regexMatch('\\\\1\\\\2', p.obj)`)\n\ttestEscapeStringLiterals(t, `regexMatch(\"\\1\\2\", p.obj)`, `regexMatch(\"\\\\1\\\\2\", p.obj)`)\n\ttestEscapeStringLiterals(t, `r.sub == '\\test'`, `r.sub == '\\\\test'`)\n\n\t// Test expressions without string literals\n\ttestEscapeStringLiterals(t, `r.sub == p.sub`, `r.sub == p.sub`)\n\ttestEscapeStringLiterals(t, `keyMatch(r.obj, p.obj)`, `keyMatch(r.obj, p.obj)`)\n\n\t// Test multiple strings in one expression\n\ttestEscapeStringLiterals(t, `regexMatch('\\1', '\\2')`, `regexMatch('\\\\1', '\\\\2')`)\n\n\t// Test empty strings\n\ttestEscapeStringLiterals(t, `''`, `''`)\n\ttestEscapeStringLiterals(t, `\"\"`, `\"\"`)\n\n\t// Test strings with no backslashes\n\ttestEscapeStringLiterals(t, `'hello'`, `'hello'`)\n\ttestEscapeStringLiterals(t, `\"world\"`, `\"world\"`)\n}\n"
  },
  {
    "path": "util_log.go",
    "content": "// Copyright 2026 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"github.com/casbin/casbin/v3/log\"\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\n// onLogBeforeEvent calls OnBeforeEvent on the logger if it exists.\nfunc (e *Enforcer) onLogBeforeEvent(eventType log.EventType) *log.LogEntry {\n\tif e.logger == nil {\n\t\treturn nil\n\t}\n\tlogEntry := &log.LogEntry{\n\t\tEventType: eventType,\n\t}\n\t_ = e.logger.OnBeforeEvent(logEntry)\n\treturn logEntry\n}\n\n// onLogAfterEvent calls OnAfterEvent on the logger if it exists and logEntry is not nil.\nfunc (e *Enforcer) onLogAfterEvent(logEntry *log.LogEntry) {\n\tif e.logger != nil && logEntry != nil {\n\t\t_ = e.logger.OnAfterEvent(logEntry)\n\t}\n}\n\n// onLogAfterEventWithError calls OnAfterEvent with an error if logger and logEntry exist.\nfunc (e *Enforcer) onLogAfterEventWithError(logEntry *log.LogEntry, err error) {\n\tif e.logger != nil && logEntry != nil {\n\t\tlogEntry.Error = err\n\t\t_ = e.logger.OnAfterEvent(logEntry)\n\t}\n}\n\n// countModelRules counts the total number of rules in a model's p and g sections.\nfunc countModelRules(m model.Model) int {\n\truleCount := 0\n\tif pSection, ok := m[\"p\"]; ok {\n\t\tfor _, ast := range pSection {\n\t\t\truleCount += len(ast.Policy)\n\t\t}\n\t}\n\tif gSection, ok := m[\"g\"]; ok {\n\t\tfor _, ast := range gSection {\n\t\t\truleCount += len(ast.Policy)\n\t\t}\n\t}\n\treturn ruleCount\n}\n\n// onLogBeforeEventInLoadPolicy initializes logging for LoadPolicy operation.\nfunc (e *Enforcer) onLogBeforeEventInLoadPolicy() *log.LogEntry {\n\treturn e.onLogBeforeEvent(log.EventLoadPolicy)\n}\n\n// onLogAfterEventInLoadPolicy finalizes logging for LoadPolicy operation with rule count.\nfunc (e *Enforcer) onLogAfterEventInLoadPolicy(logEntry *log.LogEntry, newModel model.Model) {\n\tif e.logger != nil && logEntry != nil {\n\t\tlogEntry.RuleCount = countModelRules(newModel)\n\t\t_ = e.logger.OnAfterEvent(logEntry)\n\t}\n}\n\n// onLogBeforeEventInSavePolicy initializes logging for SavePolicy operation with rule count.\nfunc (e *Enforcer) onLogBeforeEventInSavePolicy() *log.LogEntry {\n\tif e.logger == nil {\n\t\treturn nil\n\t}\n\tlogEntry := &log.LogEntry{\n\t\tEventType: log.EventSavePolicy,\n\t\tRuleCount: countModelRules(e.model),\n\t}\n\t_ = e.logger.OnBeforeEvent(logEntry)\n\treturn logEntry\n}\n\n// onLogAfterEventInSavePolicy finalizes logging for SavePolicy operation.\nfunc (e *Enforcer) onLogAfterEventInSavePolicy(logEntry *log.LogEntry) {\n\te.onLogAfterEvent(logEntry)\n}\n\n// createEnforceLogEntry creates a log entry for enforce events with subject, object, action, and domain extracted from rvals.\nfunc (e *Enforcer) createEnforceLogEntry(rvals []interface{}) *log.LogEntry {\n\tentry := &log.LogEntry{\n\t\tEventType: log.EventEnforce,\n\t}\n\tif len(rvals) > 0 {\n\t\tif s, isString := rvals[0].(string); isString {\n\t\t\tentry.Subject = s\n\t\t}\n\t}\n\tif len(rvals) > 1 {\n\t\tif o, isString := rvals[1].(string); isString {\n\t\t\tentry.Object = o\n\t\t}\n\t}\n\tif len(rvals) > 2 {\n\t\tif a, isString := rvals[2].(string); isString {\n\t\t\tentry.Action = a\n\t\t}\n\t}\n\tif len(rvals) > 3 {\n\t\tif d, isString := rvals[3].(string); isString {\n\t\t\tentry.Domain = d\n\t\t}\n\t}\n\treturn entry\n}\n\n// onLogBeforeEventInEnforce initializes logging for Enforce operation.\nfunc (e *Enforcer) onLogBeforeEventInEnforce(rvals []interface{}) *log.LogEntry {\n\tif e.logger == nil {\n\t\treturn nil\n\t}\n\tlogEntry := e.createEnforceLogEntry(rvals)\n\t_ = e.logger.OnBeforeEvent(logEntry)\n\treturn logEntry\n}\n\n// onLogAfterEventInEnforce finalizes logging for Enforce operation.\nfunc (e *Enforcer) onLogAfterEventInEnforce(logEntry *log.LogEntry, allowed bool) {\n\tif e.logger != nil && logEntry != nil {\n\t\tlogEntry.Allowed = allowed\n\t\t_ = e.logger.OnAfterEvent(logEntry)\n\t}\n}\n\n// logPolicyOperation logs a policy operation (add or remove) with before and after events.\nfunc (e *Enforcer) logPolicyOperation(eventType log.EventType, sec string, rule []string, operation func() (bool, error)) (bool, error) {\n\tvar logEntry *log.LogEntry\n\tif e.logger != nil && sec == \"p\" {\n\t\tlogEntry = &log.LogEntry{\n\t\t\tEventType: eventType,\n\t\t\tRules:     [][]string{rule},\n\t\t}\n\t\t_ = e.logger.OnBeforeEvent(logEntry)\n\t}\n\n\tok, err := operation()\n\n\tif e.logger != nil && logEntry != nil {\n\t\tif ok && err == nil {\n\t\t\tlogEntry.RuleCount = 1\n\t\t} else {\n\t\t\tlogEntry.RuleCount = 0\n\t\t\tlogEntry.Error = err\n\t\t}\n\t\t_ = e.logger.OnAfterEvent(logEntry)\n\t}\n\n\treturn ok, err\n}\n"
  },
  {
    "path": "watcher_ex_test.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n)\n\ntype SampleWatcherEx struct {\n\tSampleWatcher\n}\n\nfunc (w SampleWatcherEx) UpdateForAddPolicy(sec, ptype string, params ...string) error {\n\treturn nil\n}\nfunc (w SampleWatcherEx) UpdateForRemovePolicy(sec, ptype string, params ...string) error {\n\treturn nil\n}\n\nfunc (w SampleWatcherEx) UpdateForRemoveFilteredPolicy(sec, ptype string, fieldIndex int, fieldValues ...string) error {\n\treturn nil\n}\n\nfunc (w SampleWatcherEx) UpdateForSavePolicy(model model.Model) error {\n\treturn nil\n}\n\nfunc (w SampleWatcherEx) UpdateForAddPolicies(sec string, ptype string, rules ...[]string) error {\n\treturn nil\n}\n\nfunc (w SampleWatcherEx) UpdateForRemovePolicies(sec string, ptype string, rules ...[]string) error {\n\treturn nil\n}\n\nfunc TestSetWatcherEx(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\tsampleWatcherEx := &SampleWatcherEx{}\n\terr := e.SetWatcher(sampleWatcherEx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_ = e.SavePolicy()                              // calls watcherEx.UpdateForSavePolicy()\n\t_, _ = e.AddPolicy(\"admin\", \"data1\", \"read\")    // calls watcherEx.UpdateForAddPolicy()\n\t_, _ = e.RemovePolicy(\"admin\", \"data1\", \"read\") // calls watcherEx.UpdateForRemovePolicy()\n\t_, _ = e.RemoveFilteredPolicy(1, \"data1\")       // calls watcherEx.UpdateForRemoveFilteredPolicy()\n\t_, _ = e.RemovePolicy(\"admin\", \"data1\", \"read\") // calls watcherEx.UpdateForRemovePolicy()\n\t_, _ = e.AddGroupingPolicy(\"g:admin\", \"data1\")\n\t_, _ = e.RemoveGroupingPolicy(\"g:admin\", \"data1\")\n\t_, _ = e.AddGroupingPolicy(\"g:admin\", \"data1\")\n\t_, _ = e.RemoveFilteredGroupingPolicy(1, \"data1\")\n\t_, _ = e.AddPolicies([][]string{{\"admin\", \"data1\", \"read\"}, {\"admin\", \"data2\", \"read\"}})    // calls watcherEx.UpdateForAddPolicies()\n\t_, _ = e.RemovePolicies([][]string{{\"admin\", \"data1\", \"read\"}, {\"admin\", \"data2\", \"read\"}}) // calls watcherEx.UpdateForRemovePolicies()\n}\n"
  },
  {
    "path": "watcher_test.go",
    "content": "// Copyright 2017 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport \"testing\"\n\ntype SampleWatcher struct {\n\tcallback func(string)\n}\n\nfunc (w *SampleWatcher) Close() {\n}\n\nfunc (w *SampleWatcher) SetUpdateCallback(callback func(string)) error {\n\tw.callback = callback\n\treturn nil\n}\n\nfunc (w *SampleWatcher) Update() error {\n\tif w.callback != nil {\n\t\tw.callback(\"\")\n\t}\n\treturn nil\n}\n\nfunc TestSetWatcher(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsampleWatcher := &SampleWatcher{}\n\terr = e.SetWatcher(sampleWatcher)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = e.SavePolicy() // calls watcher.Update()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestSelfModify(t *testing.T) {\n\te, err := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsampleWatcher := &SampleWatcher{}\n\terr = e.SetWatcher(sampleWatcher)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar called int\n\n\tcalled = -1\n\t_ = e.watcher.SetUpdateCallback(func(s string) {\n\t\tcalled = 1\n\t})\n\t_, err = e.AddPolicy(\"eva\", \"data\", \"read\") // calls watcher.Update()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called != 1 {\n\t\tt.Fatal(\"callback should be called\")\n\t}\n\n\tcalled = -1\n\t_ = e.watcher.SetUpdateCallback(func(s string) {\n\t\tcalled = 1\n\t})\n\t_, err = e.SelfAddPolicy(\"p\", \"p\", []string{\"eva\", \"data\", \"write\"}) // calls watcher.Update()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif called != -1 {\n\t\tt.Fatal(\"callback should not be called\")\n\t}\n}\n"
  },
  {
    "path": "watcher_update_test.go",
    "content": "// Copyright 2020 The casbin Authors. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage casbin\n\nimport (\n\t\"testing\"\n)\n\ntype SampleWatcherUpdatable struct {\n\tSampleWatcher\n}\n\nfunc (w SampleWatcherUpdatable) UpdateForUpdatePolicy(params ...string) error {\n\treturn nil\n}\n\nfunc TestSetWatcherUpdatable(t *testing.T) {\n\te, _ := NewEnforcer(\"examples/rbac_model.conf\", \"examples/rbac_policy.csv\")\n\n\tsampleWatcherEx := &SampleWatcherUpdatable{}\n\terr := e.SetWatcher(sampleWatcherEx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_ = e.SavePolicy()                                                                            // calls watcherEx.UpdateForSavePolicy()\n\t_, _ = e.UpdatePolicy([]string{\"admin\", \"data1\", \"read\"}, []string{\"admin\", \"data2\", \"read\"}) // calls watcherEx.UpdateForUpdatePolicy()\n}\n"
  }
]