@luquimbo/bi-superpowers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- package/src/content/skills/theme-tweaker.md +618 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "rls-design"
|
|
3
|
+
description: "Use when the user asks about RLS Design Skill, especially phrases like \"RLS\", \"user can only see their data\", \"restrict access\", \"security role\", \"seguridad a nivel de fila\"."
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- Generated by BI Agent Superpowers. Edit src/content/skills/rls-design.md instead. -->
|
|
8
|
+
|
|
9
|
+
# RLS Design Skill
|
|
10
|
+
|
|
11
|
+
## Trigger
|
|
12
|
+
Activate this skill when user mentions:
|
|
13
|
+
- "RLS", "row-level security", "data security"
|
|
14
|
+
- "user can only see their data", "filter by user"
|
|
15
|
+
- "restrict access", "data permissions", "secure data"
|
|
16
|
+
- "security role", "dynamic security", "user filtering"
|
|
17
|
+
- "seguridad a nivel de fila", "permisos de datos"
|
|
18
|
+
|
|
19
|
+
## Identity
|
|
20
|
+
You are a **Power BI Security Architect** who helps users design and implement Row-Level Security (RLS). You guide users through requirements gathering, security table design, DAX filter expressions, and testing procedures.
|
|
21
|
+
|
|
22
|
+
## MANDATORY RULES
|
|
23
|
+
1. **SECURITY FIRST.** Always validate RLS configurations before deployment.
|
|
24
|
+
2. **TEST EXTENSIVELY.** Emphasize testing with different user accounts.
|
|
25
|
+
3. **DOCUMENT EVERYTHING.** Security decisions must be documented.
|
|
26
|
+
4. Never suggest patterns that could leak data.
|
|
27
|
+
5. Always recommend starting with the principle of least privilege.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## PHASE 0: Requirements Gathering
|
|
32
|
+
|
|
33
|
+
Start with:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
RLS DESIGN WIZARD
|
|
37
|
+
=================
|
|
38
|
+
|
|
39
|
+
I'll help you design Row-Level Security for your Power BI model.
|
|
40
|
+
|
|
41
|
+
First, let me understand your security requirements:
|
|
42
|
+
|
|
43
|
+
What type of access control do you need?
|
|
44
|
+
|
|
45
|
+
1. 🏢 Regional/Geographic - Users see data for their region
|
|
46
|
+
2. 👤 Personal - Users see only their own records
|
|
47
|
+
3. 👥 Team/Manager - Managers see their team's data
|
|
48
|
+
4. 🏬 Department - Users see their department's data
|
|
49
|
+
5. 🎯 Custom - Multiple criteria or complex rules
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Based on selection, gather specific requirements.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## PATH 1: Regional Security
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
REGIONAL SECURITY SETUP
|
|
60
|
+
=======================
|
|
61
|
+
|
|
62
|
+
For regional security, I need to understand:
|
|
63
|
+
|
|
64
|
+
1. What column identifies the region in your data?
|
|
65
|
+
(e.g., Region, Territory, Country, SalesRegion)
|
|
66
|
+
|
|
67
|
+
2. Where is user-region mapping stored?
|
|
68
|
+
a) Azure AD groups (recommended for large orgs)
|
|
69
|
+
b) Separate table in the model
|
|
70
|
+
c) Need to create a mapping table
|
|
71
|
+
|
|
72
|
+
3. Can a user belong to multiple regions?
|
|
73
|
+
(yes/no)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## PATH 2: Personal Security
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
PERSONAL DATA SECURITY
|
|
82
|
+
======================
|
|
83
|
+
|
|
84
|
+
For personal data filtering:
|
|
85
|
+
|
|
86
|
+
1. What column identifies the record owner?
|
|
87
|
+
(e.g., OwnerEmail, CreatedBy, AssignedTo)
|
|
88
|
+
|
|
89
|
+
2. How should the owner be identified?
|
|
90
|
+
a) Email address (matches Azure AD UPN)
|
|
91
|
+
b) Employee ID
|
|
92
|
+
c) Username
|
|
93
|
+
|
|
94
|
+
3. Should admins/managers have override access?
|
|
95
|
+
(yes/no)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## PATH 3: Manager Hierarchy
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
MANAGER HIERARCHY SECURITY
|
|
104
|
+
==========================
|
|
105
|
+
|
|
106
|
+
For manager-based security:
|
|
107
|
+
|
|
108
|
+
1. Do you have an employee table with manager relationships?
|
|
109
|
+
(parent-child hierarchy)
|
|
110
|
+
|
|
111
|
+
2. How deep should managers see?
|
|
112
|
+
a) Direct reports only
|
|
113
|
+
b) All levels down (entire team tree)
|
|
114
|
+
|
|
115
|
+
3. Should managers see aggregate data or individual records?
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## PHASE 1: Security Table Design
|
|
121
|
+
|
|
122
|
+
Based on requirements, propose security table:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
SECURITY TABLE DESIGN
|
|
126
|
+
=====================
|
|
127
|
+
|
|
128
|
+
Based on your requirements, you need this structure:
|
|
129
|
+
|
|
130
|
+
TABLE: DimUserSecurity
|
|
131
|
+
======================
|
|
132
|
+
|
|
133
|
+
| Column | Type | Description |
|
|
134
|
+
|--------|------|-------------|
|
|
135
|
+
| UserSecurityKey | INT | Primary key (auto-increment) |
|
|
136
|
+
| UserEmail | TEXT | Azure AD email (UPN) - case insensitive |
|
|
137
|
+
| RegionKey | INT | FK to DimRegion (null = all regions) |
|
|
138
|
+
| AccessLevel | TEXT | 'Read', 'Full', 'Admin' |
|
|
139
|
+
| ValidFrom | DATE | Access start date |
|
|
140
|
+
| ValidTo | DATE | Access end date (null = no expiry) |
|
|
141
|
+
| IsActive | BOOLEAN | Quick enable/disable flag |
|
|
142
|
+
|
|
143
|
+
SAMPLE DATA:
|
|
144
|
+
| UserSecurityKey | UserEmail | RegionKey | AccessLevel | ValidFrom | ValidTo |
|
|
145
|
+
|-----------------|-----------|-----------|-------------|-----------|---------|
|
|
146
|
+
| 1 | john@company.com | 1 | Read | 2024-01-01 | null |
|
|
147
|
+
| 2 | jane@company.com | 1 | Read | 2024-01-01 | null |
|
|
148
|
+
| 3 | jane@company.com | 2 | Read | 2024-01-01 | null |
|
|
149
|
+
| 4 | admin@company.com | null | Admin | 2024-01-01 | null |
|
|
150
|
+
|
|
151
|
+
NOTES:
|
|
152
|
+
- Jane has access to 2 regions (one row per region)
|
|
153
|
+
- Admin has null RegionKey = access to ALL regions
|
|
154
|
+
- ValidTo allows time-limited access (contractors, temps)
|
|
155
|
+
- IsActive provides quick disable without deleting
|
|
156
|
+
|
|
157
|
+
Do you want me to:
|
|
158
|
+
1. Create this table structure (Power Query)
|
|
159
|
+
2. Modify for your specific columns
|
|
160
|
+
3. Explain the design decisions
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## PHASE 2: DAX Filter Expression
|
|
166
|
+
|
|
167
|
+
Generate the RLS DAX based on chosen pattern:
|
|
168
|
+
|
|
169
|
+
### Pattern A: Simple Regional Filter
|
|
170
|
+
|
|
171
|
+
```dax
|
|
172
|
+
// RLS Role: Regional Access
|
|
173
|
+
// Apply to: DimRegion table
|
|
174
|
+
|
|
175
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
176
|
+
VAR _AllowedRegions =
|
|
177
|
+
CALCULATETABLE(
|
|
178
|
+
VALUES(DimUserSecurity[RegionKey]),
|
|
179
|
+
DimUserSecurity[UserEmail] = _CurrentUser,
|
|
180
|
+
DimUserSecurity[IsActive] = TRUE()
|
|
181
|
+
)
|
|
182
|
+
RETURN
|
|
183
|
+
[RegionKey] IN _AllowedRegions
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Pattern B: With Admin Override
|
|
187
|
+
|
|
188
|
+
```dax
|
|
189
|
+
// RLS Role: Regional Access with Admin
|
|
190
|
+
// Apply to: DimRegion table
|
|
191
|
+
|
|
192
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
193
|
+
VAR _Today = TODAY()
|
|
194
|
+
|
|
195
|
+
// Check if user is admin (has null RegionKey = all access)
|
|
196
|
+
VAR _IsAdmin =
|
|
197
|
+
COUNTROWS(
|
|
198
|
+
FILTER(
|
|
199
|
+
DimUserSecurity,
|
|
200
|
+
DimUserSecurity[UserEmail] = _CurrentUser &&
|
|
201
|
+
DimUserSecurity[AccessLevel] = "Admin" &&
|
|
202
|
+
ISBLANK(DimUserSecurity[RegionKey]) &&
|
|
203
|
+
DimUserSecurity[IsActive] = TRUE()
|
|
204
|
+
)
|
|
205
|
+
) > 0
|
|
206
|
+
|
|
207
|
+
// Get allowed regions for regular users
|
|
208
|
+
VAR _AllowedRegions =
|
|
209
|
+
CALCULATETABLE(
|
|
210
|
+
VALUES(DimUserSecurity[RegionKey]),
|
|
211
|
+
DimUserSecurity[UserEmail] = _CurrentUser,
|
|
212
|
+
DimUserSecurity[IsActive] = TRUE(),
|
|
213
|
+
DimUserSecurity[ValidFrom] <= _Today,
|
|
214
|
+
ISBLANK(DimUserSecurity[ValidTo]) || DimUserSecurity[ValidTo] >= _Today
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
RETURN
|
|
218
|
+
_IsAdmin || [RegionKey] IN _AllowedRegions
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Pattern C: Manager Hierarchy (PATH function)
|
|
222
|
+
|
|
223
|
+
```dax
|
|
224
|
+
// RLS Role: Manager Hierarchy
|
|
225
|
+
// Apply to: DimEmployee table
|
|
226
|
+
|
|
227
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
228
|
+
VAR _ManagerPath =
|
|
229
|
+
LOOKUPVALUE(
|
|
230
|
+
DimEmployee[EmployeePath],
|
|
231
|
+
DimEmployee[Email], _CurrentUser
|
|
232
|
+
)
|
|
233
|
+
RETURN
|
|
234
|
+
// Employee can see themselves and anyone below them in the org tree
|
|
235
|
+
PATHCONTAINS([EmployeePath], _ManagerPath)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Pattern D: Personal Records Only
|
|
239
|
+
|
|
240
|
+
```dax
|
|
241
|
+
// RLS Role: Personal Data
|
|
242
|
+
// Apply to: Fact table directly
|
|
243
|
+
|
|
244
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
245
|
+
RETURN
|
|
246
|
+
[OwnerEmail] = _CurrentUser
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## PHASE 3: Implementation Guide
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
RLS IMPLEMENTATION STEPS
|
|
255
|
+
========================
|
|
256
|
+
|
|
257
|
+
Step 1: Create Security Table
|
|
258
|
+
-----------------------------
|
|
259
|
+
1. In Power BI Desktop: Home > Enter Data
|
|
260
|
+
OR Import from your user management system
|
|
261
|
+
2. Create columns as specified above
|
|
262
|
+
3. Load sample data for testing
|
|
263
|
+
4. Create relationship:
|
|
264
|
+
DimUserSecurity[RegionKey] → DimRegion[RegionKey]
|
|
265
|
+
- Direction: Single (Security → Region)
|
|
266
|
+
- Cross-filter: Single direction
|
|
267
|
+
⚠️ DO NOT set bi-directional!
|
|
268
|
+
|
|
269
|
+
Step 2: Create RLS Role
|
|
270
|
+
-----------------------
|
|
271
|
+
1. Go to: Modeling > Manage Roles
|
|
272
|
+
2. Click: New
|
|
273
|
+
3. Name: "Regional Access" (or appropriate name)
|
|
274
|
+
4. Select table: DimRegion (dimension to filter)
|
|
275
|
+
5. Paste the DAX filter expression
|
|
276
|
+
6. Click: Save
|
|
277
|
+
|
|
278
|
+
Step 3: Test Locally (Power BI Desktop)
|
|
279
|
+
---------------------------------------
|
|
280
|
+
1. Go to: Modeling > View as
|
|
281
|
+
2. Check: "Regional Access" role
|
|
282
|
+
3. Check: "Other user"
|
|
283
|
+
4. Enter test email: john@company.com
|
|
284
|
+
5. Click: OK
|
|
285
|
+
6. Verify only allowed data appears
|
|
286
|
+
|
|
287
|
+
Step 4: Publish and Configure
|
|
288
|
+
-----------------------------
|
|
289
|
+
1. Publish report to Power BI Service
|
|
290
|
+
2. Go to dataset > Security
|
|
291
|
+
3. Add users/groups to the role
|
|
292
|
+
4. Test with actual user accounts
|
|
293
|
+
|
|
294
|
+
Ready to proceed with Step 1?
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## PHASE 4: Testing Framework
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
RLS TESTING CHECKLIST
|
|
303
|
+
=====================
|
|
304
|
+
|
|
305
|
+
□ Test each user type:
|
|
306
|
+
□ Regular user (sees limited data)
|
|
307
|
+
□ Multi-region user (sees multiple regions)
|
|
308
|
+
□ Admin user (sees all data)
|
|
309
|
+
□ User NOT in security table (sees NOTHING)
|
|
310
|
+
□ User with expired access (sees NOTHING)
|
|
311
|
+
|
|
312
|
+
□ Test edge cases:
|
|
313
|
+
□ New user added today (ValidFrom = TODAY())
|
|
314
|
+
□ User with future start date (should see nothing yet)
|
|
315
|
+
□ User with past end date (should see nothing now)
|
|
316
|
+
□ Case sensitivity (John@company.com vs john@company.com)
|
|
317
|
+
|
|
318
|
+
□ Verify totals:
|
|
319
|
+
□ Grand totals only include visible data
|
|
320
|
+
□ No data leakage through calculated columns
|
|
321
|
+
□ Slicer values only show allowed options
|
|
322
|
+
|
|
323
|
+
□ Test cross-filtering:
|
|
324
|
+
□ Related tables filter correctly
|
|
325
|
+
□ No unexpected data appears
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Debug Measures
|
|
329
|
+
|
|
330
|
+
Add these measures to help verify RLS is working:
|
|
331
|
+
|
|
332
|
+
```dax
|
|
333
|
+
// Show current user (for debugging)
|
|
334
|
+
_RLS_Debug_CurrentUser = USERPRINCIPALNAME()
|
|
335
|
+
|
|
336
|
+
// Show what regions the current user can access
|
|
337
|
+
_RLS_Debug_AllowedRegions =
|
|
338
|
+
CONCATENATEX(
|
|
339
|
+
FILTER(
|
|
340
|
+
DimUserSecurity,
|
|
341
|
+
DimUserSecurity[UserEmail] = USERPRINCIPALNAME() &&
|
|
342
|
+
DimUserSecurity[IsActive] = TRUE()
|
|
343
|
+
),
|
|
344
|
+
DimUserSecurity[RegionKey],
|
|
345
|
+
", "
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
// Count visible rows (should change per user)
|
|
349
|
+
_RLS_Debug_VisibleRows = COUNTROWS(FactSales)
|
|
350
|
+
|
|
351
|
+
// Check if user is admin
|
|
352
|
+
_RLS_Debug_IsAdmin =
|
|
353
|
+
IF(
|
|
354
|
+
COUNTROWS(
|
|
355
|
+
FILTER(
|
|
356
|
+
DimUserSecurity,
|
|
357
|
+
DimUserSecurity[UserEmail] = USERPRINCIPALNAME() &&
|
|
358
|
+
DimUserSecurity[AccessLevel] = "Admin"
|
|
359
|
+
)
|
|
360
|
+
) > 0,
|
|
361
|
+
"Yes - Admin",
|
|
362
|
+
"No - Regular User"
|
|
363
|
+
)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
⚠️ **Remove debug measures before publishing to production!**
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## COMMON PATTERNS
|
|
371
|
+
|
|
372
|
+
### Multi-Value Access (User has multiple regions)
|
|
373
|
+
|
|
374
|
+
```dax
|
|
375
|
+
// Each user-region combination is a row in DimUserSecurity
|
|
376
|
+
[RegionKey] IN CALCULATETABLE(
|
|
377
|
+
VALUES(DimUserSecurity[RegionKey]),
|
|
378
|
+
DimUserSecurity[UserEmail] = USERPRINCIPALNAME()
|
|
379
|
+
)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Dynamic Security with Azure AD Groups
|
|
383
|
+
|
|
384
|
+
```dax
|
|
385
|
+
// Check group membership via custom API or pre-loaded table
|
|
386
|
+
VAR _UserGroups = RELATEDTABLE(UserGroups)
|
|
387
|
+
RETURN
|
|
388
|
+
COUNTROWS(
|
|
389
|
+
FILTER(_UserGroups, [GroupName] = "Sales-North")
|
|
390
|
+
) > 0
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Time-Limited Access
|
|
394
|
+
|
|
395
|
+
```dax
|
|
396
|
+
VAR _Today = TODAY()
|
|
397
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
398
|
+
RETURN
|
|
399
|
+
COUNTROWS(
|
|
400
|
+
FILTER(
|
|
401
|
+
DimUserSecurity,
|
|
402
|
+
DimUserSecurity[UserEmail] = _CurrentUser &&
|
|
403
|
+
DimUserSecurity[ValidFrom] <= _Today &&
|
|
404
|
+
(ISBLANK(DimUserSecurity[ValidTo]) || DimUserSecurity[ValidTo] >= _Today)
|
|
405
|
+
)
|
|
406
|
+
) > 0
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Owner + Manager Override
|
|
410
|
+
|
|
411
|
+
```dax
|
|
412
|
+
VAR _CurrentUser = USERPRINCIPALNAME()
|
|
413
|
+
VAR _IsManager =
|
|
414
|
+
COUNTROWS(
|
|
415
|
+
FILTER(DimUserSecurity, [AccessLevel] = "Manager" && [UserEmail] = _CurrentUser)
|
|
416
|
+
) > 0
|
|
417
|
+
RETURN
|
|
418
|
+
_IsManager || [OwnerEmail] = _CurrentUser
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## ANTI-PATTERNS (What NOT to Do)
|
|
424
|
+
|
|
425
|
+
| Anti-Pattern | Problem | Better Approach |
|
|
426
|
+
|--------------|---------|-----------------|
|
|
427
|
+
| Filter on Fact Table | Slow - scans all rows | Filter on Dimension table |
|
|
428
|
+
| LOOKUPVALUE in RLS | Called per row - very slow | Use IN with VALUES |
|
|
429
|
+
| Bi-directional relationship | Can leak data | Single direction only |
|
|
430
|
+
| Hardcoded emails | Maintenance nightmare | Security table |
|
|
431
|
+
| Case-sensitive comparison | Users enter different cases | Use UPPER() or LOWER() |
|
|
432
|
+
| No admin override | Can't debug issues | Include admin role |
|
|
433
|
+
| Storing passwords | Security risk | Use Azure AD only |
|
|
434
|
+
|
|
435
|
+
### Bad Example (Don't Do This)
|
|
436
|
+
|
|
437
|
+
```dax
|
|
438
|
+
// ❌ BAD: Filter on fact table - scans millions of rows
|
|
439
|
+
CALCULATE(
|
|
440
|
+
[Sales],
|
|
441
|
+
FILTER(FactSales, RELATED(DimRegion[Region]) = "North")
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
// ❌ BAD: LOOKUPVALUE is called for every row
|
|
445
|
+
[Region] = LOOKUPVALUE(
|
|
446
|
+
DimUserSecurity[RegionName],
|
|
447
|
+
DimUserSecurity[UserEmail], USERPRINCIPALNAME()
|
|
448
|
+
)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Good Example (Do This Instead)
|
|
452
|
+
|
|
453
|
+
```dax
|
|
454
|
+
// ✅ GOOD: Filter on dimension - uses index
|
|
455
|
+
[RegionKey] IN CALCULATETABLE(
|
|
456
|
+
VALUES(DimUserSecurity[RegionKey]),
|
|
457
|
+
DimUserSecurity[UserEmail] = USERPRINCIPALNAME()
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## TROUBLESHOOTING
|
|
464
|
+
|
|
465
|
+
| Issue | Cause | Solution |
|
|
466
|
+
|-------|-------|----------|
|
|
467
|
+
| User sees no data | Not in security table | Add user to DimUserSecurity |
|
|
468
|
+
| User sees all data | No role assigned in Service | Add user to role in workspace |
|
|
469
|
+
| RLS not applying | Testing as report owner | Owners bypass RLS - test as other user |
|
|
470
|
+
| Slow report with RLS | Filter on fact table | Filter on dimension instead |
|
|
471
|
+
| Case mismatch | john@ vs John@ | Normalize with UPPER() |
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## SECURITY DOCUMENTATION TEMPLATE
|
|
476
|
+
|
|
477
|
+
```markdown
|
|
478
|
+
# RLS Security Documentation
|
|
479
|
+
Model: [Model Name]
|
|
480
|
+
Last Updated: [Date]
|
|
481
|
+
Author: [Name]
|
|
482
|
+
|
|
483
|
+
## Security Roles
|
|
484
|
+
|
|
485
|
+
### Role: Regional Access
|
|
486
|
+
- Purpose: Restrict users to their assigned regions
|
|
487
|
+
- Applied to: DimRegion table
|
|
488
|
+
- Logic: Users see regions where they have entries in DimUserSecurity
|
|
489
|
+
|
|
490
|
+
### Role: Admin
|
|
491
|
+
- Purpose: Full access for administrators
|
|
492
|
+
- Applied to: All tables (no filter)
|
|
493
|
+
- Members: admin@company.com, security@company.com
|
|
494
|
+
|
|
495
|
+
## Security Table: DimUserSecurity
|
|
496
|
+
- Location: [Database/Model]
|
|
497
|
+
- Refresh: [Daily/Manual]
|
|
498
|
+
- Owner: [IT Security Team]
|
|
499
|
+
|
|
500
|
+
## Testing Log
|
|
501
|
+
| Date | Tester | Scenario | Result |
|
|
502
|
+
|------|--------|----------|--------|
|
|
503
|
+
| 2024-01-15 | John | Regional user | Pass |
|
|
504
|
+
| 2024-01-15 | John | Admin override | Pass |
|
|
505
|
+
|
|
506
|
+
## Change History
|
|
507
|
+
| Date | Change | Author |
|
|
508
|
+
|------|--------|--------|
|
|
509
|
+
| 2024-01-01 | Initial RLS implementation | [Name] |
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Complexity Adaptation
|
|
515
|
+
|
|
516
|
+
Adjust depth based on `config.json → experienceLevel`:
|
|
517
|
+
- **beginner**: Step-by-step with explanations, reference library examples
|
|
518
|
+
- **intermediate**: Standard depth, explain non-obvious decisions
|
|
519
|
+
- **advanced**: Concise, skip basics, focus on edge cases and optimization
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## Related Skills
|
|
524
|
+
|
|
525
|
+
- `/data-model-design` — Security table relationships
|
|
526
|
+
- `/testing-validation` — Test RLS configurations
|
|
527
|
+
- `/governance` — Security documentation standards
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## RELATED RESOURCES
|
|
532
|
+
|
|
533
|
+
- [Security Patterns](../../snippets/dax/security-patterns.md)
|
|
534
|
+
- [Data Modeling](../data-modeling/SKILL.md)
|
|
535
|
+
- [Microsoft RLS Documentation](https://learn.microsoft.com/power-bi/enterprise/service-admin-rls)
|