mirror of
https://github.com/github/awesome-copilot.git
synced 2026-02-23 20:05:12 +00:00
Add Power BI modeling skill
This commit is contained in:
226
skills/powerbi-modeling/references/RLS.md
Normal file
226
skills/powerbi-modeling/references/RLS.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# Row-Level Security (RLS) in Power BI
|
||||
|
||||
## Overview
|
||||
|
||||
Row-Level Security restricts data access at the row level based on user identity. Users see only the data they're authorized to view.
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. Filter on Dimension Tables
|
||||
Apply RLS to dimensions, not fact tables:
|
||||
- More efficient (smaller tables)
|
||||
- Filters propagate through relationships
|
||||
- Easier to maintain
|
||||
|
||||
```dax
|
||||
// On Customer dimension - filters propagate to Sales
|
||||
[Region] = "West"
|
||||
```
|
||||
|
||||
### 2. Create Minimal Roles
|
||||
Avoid many role combinations:
|
||||
- Each role = separate cache
|
||||
- Roles are additive (union, not intersection)
|
||||
- Consolidate where possible
|
||||
|
||||
### 3. Use Dynamic RLS When Possible
|
||||
Data-driven rules scale better:
|
||||
- User mapping in a table
|
||||
- USERPRINCIPALNAME() for identity
|
||||
- No role changes when users change
|
||||
|
||||
## Static vs Dynamic RLS
|
||||
|
||||
### Static RLS
|
||||
Fixed rules per role:
|
||||
```dax
|
||||
// Role: West Region
|
||||
[Region] = "West"
|
||||
|
||||
// Role: East Region
|
||||
[Region] = "East"
|
||||
```
|
||||
|
||||
**Pros:** Simple, clear
|
||||
**Cons:** Doesn't scale, requires role per group
|
||||
|
||||
### Dynamic RLS
|
||||
User identity drives filtering:
|
||||
```dax
|
||||
// Single role filters based on logged-in user
|
||||
[ManagerEmail] = USERPRINCIPALNAME()
|
||||
```
|
||||
|
||||
**Pros:** Scales, self-maintaining
|
||||
**Cons:** Requires user mapping data
|
||||
|
||||
## Implementation Patterns
|
||||
|
||||
### Pattern 1: Direct User Mapping
|
||||
User email in dimension table:
|
||||
```dax
|
||||
// On Customer table
|
||||
[CustomerEmail] = USERPRINCIPALNAME()
|
||||
```
|
||||
|
||||
### Pattern 2: Security Table
|
||||
Separate table mapping users to data:
|
||||
```
|
||||
SecurityMapping table:
|
||||
| UserEmail | Region |
|
||||
|-----------|--------|
|
||||
| joe@co.com | West |
|
||||
| sue@co.com | East |
|
||||
```
|
||||
|
||||
```dax
|
||||
// On Region dimension
|
||||
[Region] IN
|
||||
SELECTCOLUMNS(
|
||||
FILTER(SecurityMapping, [UserEmail] = USERPRINCIPALNAME()),
|
||||
"Region", [Region]
|
||||
)
|
||||
```
|
||||
|
||||
### Pattern 3: Manager Hierarchy
|
||||
Users see their data plus subordinates:
|
||||
```dax
|
||||
// Using PATH functions for hierarchy
|
||||
PATHCONTAINS(Employee[ManagerPath],
|
||||
LOOKUPVALUE(Employee[EmployeeID], Employee[Email], USERPRINCIPALNAME()))
|
||||
```
|
||||
|
||||
### Pattern 4: Multiple Rules
|
||||
Combine conditions:
|
||||
```dax
|
||||
// Users see their region OR if they're a global viewer
|
||||
[Region] = LOOKUPVALUE(Users[Region], Users[Email], USERPRINCIPALNAME())
|
||||
|| LOOKUPVALUE(Users[IsGlobal], Users[Email], USERPRINCIPALNAME()) = TRUE()
|
||||
```
|
||||
|
||||
## Creating Roles via MCP
|
||||
|
||||
### List Existing Roles
|
||||
```
|
||||
security_role_operations(operation: "List")
|
||||
```
|
||||
|
||||
### Create Role with Permission
|
||||
```
|
||||
security_role_operations(
|
||||
operation: "Create",
|
||||
definitions: [{
|
||||
name: "Regional Sales",
|
||||
modelPermission: "Read",
|
||||
description: "Restricts sales data by region"
|
||||
}]
|
||||
)
|
||||
```
|
||||
|
||||
### Add Table Permission (Filter)
|
||||
```
|
||||
security_role_operations(
|
||||
operation: "CreatePermissions",
|
||||
permissionDefinitions: [{
|
||||
roleName: "Regional Sales",
|
||||
tableName: "Customer",
|
||||
filterExpression: "[Region] = USERPRINCIPALNAME()"
|
||||
}]
|
||||
)
|
||||
```
|
||||
|
||||
### Get Effective Permissions
|
||||
```
|
||||
security_role_operations(
|
||||
operation: "GetEffectivePermissions",
|
||||
references: [{ name: "Regional Sales" }]
|
||||
)
|
||||
```
|
||||
|
||||
## Testing RLS
|
||||
|
||||
### In Power BI Desktop
|
||||
1. Modeling tab > View As
|
||||
2. Select role(s) to test
|
||||
3. Optionally specify user identity
|
||||
4. Verify data filtering
|
||||
|
||||
### Test Unexpected Values
|
||||
For dynamic RLS, test:
|
||||
- Valid users
|
||||
- Unknown users (should see nothing or error gracefully)
|
||||
- NULL/blank values
|
||||
|
||||
```dax
|
||||
// Defensive pattern - returns no data for unknown users
|
||||
IF(
|
||||
USERPRINCIPALNAME() IN VALUES(SecurityMapping[UserEmail]),
|
||||
[Region] IN SELECTCOLUMNS(...),
|
||||
FALSE()
|
||||
)
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. RLS on Fact Tables Only
|
||||
**Problem:** Large table scans, poor performance
|
||||
**Solution:** Apply to dimension tables, let relationships propagate
|
||||
|
||||
### 2. Using LOOKUPVALUE Instead of Relationships
|
||||
**Problem:** Expensive, doesn't scale
|
||||
**Solution:** Create proper relationships, let filters flow
|
||||
|
||||
### 3. Expecting Intersection Behavior
|
||||
**Problem:** Multiple roles = UNION (additive), not intersection
|
||||
**Solution:** Design roles with union behavior in mind
|
||||
|
||||
### 4. Forgetting About DirectQuery
|
||||
**Problem:** RLS filters become WHERE clauses
|
||||
**Solution:** Ensure source database can handle the query patterns
|
||||
|
||||
### 5. Not Testing Edge Cases
|
||||
**Problem:** Users see unexpected data
|
||||
**Solution:** Test with: valid users, invalid users, multiple roles
|
||||
|
||||
## Bidirectional RLS
|
||||
|
||||
For bidirectional relationships with RLS:
|
||||
```
|
||||
Enable "Apply security filter in both directions"
|
||||
```
|
||||
|
||||
Only use when:
|
||||
- RLS requires filtering through many-to-many
|
||||
- Dimension-to-dimension security needed
|
||||
|
||||
**Caution:** Only one bidirectional relationship per path allowed.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- RLS adds WHERE clauses to every query
|
||||
- Complex DAX in filters hurts performance
|
||||
- Test with realistic user counts
|
||||
- Consider aggregations for large models
|
||||
|
||||
## Object-Level Security (OLS)
|
||||
|
||||
Restrict access to entire tables or columns:
|
||||
```
|
||||
// Via XMLA/TMSL - not available in Desktop UI
|
||||
```
|
||||
|
||||
Use for:
|
||||
- Hiding sensitive columns (salary, SSN)
|
||||
- Restricting entire tables
|
||||
- Combined with RLS for comprehensive security
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] RLS applied to dimension tables (not fact tables)
|
||||
- [ ] Filters propagate correctly through relationships
|
||||
- [ ] Dynamic RLS uses USERPRINCIPALNAME()
|
||||
- [ ] Tested with valid and invalid users
|
||||
- [ ] Edge cases handled (NULL, unknown users)
|
||||
- [ ] Performance tested under load
|
||||
- [ ] Role mappings documented
|
||||
- [ ] Workspace roles understood (Admins bypass RLS)
|
||||
Reference in New Issue
Block a user